test_hstore.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import json
  2. from django.core import checks, exceptions, serializers
  3. from django.forms import Form
  4. from django.test.utils import isolate_apps
  5. from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
  6. from .models import HStoreModel, PostgreSQLModel
  7. try:
  8. from django.contrib.postgres import forms
  9. from django.contrib.postgres.fields import HStoreField
  10. from django.contrib.postgres.validators import KeysValidator
  11. except ImportError:
  12. pass
  13. class SimpleTests(PostgreSQLTestCase):
  14. def test_save_load_success(self):
  15. value = {'a': 'b'}
  16. instance = HStoreModel(field=value)
  17. instance.save()
  18. reloaded = HStoreModel.objects.get()
  19. self.assertEqual(reloaded.field, value)
  20. def test_null(self):
  21. instance = HStoreModel(field=None)
  22. instance.save()
  23. reloaded = HStoreModel.objects.get()
  24. self.assertIsNone(reloaded.field)
  25. def test_value_null(self):
  26. value = {'a': None}
  27. instance = HStoreModel(field=value)
  28. instance.save()
  29. reloaded = HStoreModel.objects.get()
  30. self.assertEqual(reloaded.field, value)
  31. def test_key_val_cast_to_string(self):
  32. value = {'a': 1, 'b': 'B', 2: 'c', 'ï': 'ê'}
  33. expected_value = {'a': '1', 'b': 'B', '2': 'c', 'ï': 'ê'}
  34. instance = HStoreModel.objects.create(field=value)
  35. instance = HStoreModel.objects.get()
  36. self.assertEqual(instance.field, expected_value)
  37. instance = HStoreModel.objects.get(field__a=1)
  38. self.assertEqual(instance.field, expected_value)
  39. instance = HStoreModel.objects.get(field__has_keys=[2, 'a', 'ï'])
  40. self.assertEqual(instance.field, expected_value)
  41. def test_array_field(self):
  42. value = [
  43. {'a': 1, 'b': 'B', 2: 'c', 'ï': 'ê'},
  44. {'a': 1, 'b': 'B', 2: 'c', 'ï': 'ê'},
  45. ]
  46. expected_value = [
  47. {'a': '1', 'b': 'B', '2': 'c', 'ï': 'ê'},
  48. {'a': '1', 'b': 'B', '2': 'c', 'ï': 'ê'},
  49. ]
  50. instance = HStoreModel.objects.create(array_field=value)
  51. instance.refresh_from_db()
  52. self.assertEqual(instance.array_field, expected_value)
  53. class TestQuerying(PostgreSQLTestCase):
  54. def setUp(self):
  55. self.objs = [
  56. HStoreModel.objects.create(field={'a': 'b'}),
  57. HStoreModel.objects.create(field={'a': 'b', 'c': 'd'}),
  58. HStoreModel.objects.create(field={'c': 'd'}),
  59. HStoreModel.objects.create(field={}),
  60. HStoreModel.objects.create(field=None),
  61. ]
  62. def test_exact(self):
  63. self.assertSequenceEqual(
  64. HStoreModel.objects.filter(field__exact={'a': 'b'}),
  65. self.objs[:1]
  66. )
  67. def test_contained_by(self):
  68. self.assertSequenceEqual(
  69. HStoreModel.objects.filter(field__contained_by={'a': 'b', 'c': 'd'}),
  70. self.objs[:4]
  71. )
  72. def test_contains(self):
  73. self.assertSequenceEqual(
  74. HStoreModel.objects.filter(field__contains={'a': 'b'}),
  75. self.objs[:2]
  76. )
  77. def test_in_generator(self):
  78. def search():
  79. yield {'a': 'b'}
  80. self.assertSequenceEqual(
  81. HStoreModel.objects.filter(field__in=search()),
  82. self.objs[:1]
  83. )
  84. def test_has_key(self):
  85. self.assertSequenceEqual(
  86. HStoreModel.objects.filter(field__has_key='c'),
  87. self.objs[1:3]
  88. )
  89. def test_has_keys(self):
  90. self.assertSequenceEqual(
  91. HStoreModel.objects.filter(field__has_keys=['a', 'c']),
  92. self.objs[1:2]
  93. )
  94. def test_has_any_keys(self):
  95. self.assertSequenceEqual(
  96. HStoreModel.objects.filter(field__has_any_keys=['a', 'c']),
  97. self.objs[:3]
  98. )
  99. def test_key_transform(self):
  100. self.assertSequenceEqual(
  101. HStoreModel.objects.filter(field__a='b'),
  102. self.objs[:2]
  103. )
  104. def test_keys(self):
  105. self.assertSequenceEqual(
  106. HStoreModel.objects.filter(field__keys=['a']),
  107. self.objs[:1]
  108. )
  109. def test_values(self):
  110. self.assertSequenceEqual(
  111. HStoreModel.objects.filter(field__values=['b']),
  112. self.objs[:1]
  113. )
  114. def test_field_chaining(self):
  115. self.assertSequenceEqual(
  116. HStoreModel.objects.filter(field__a__contains='b'),
  117. self.objs[:2]
  118. )
  119. def test_order_by_field(self):
  120. more_objs = (
  121. HStoreModel.objects.create(field={'g': '637'}),
  122. HStoreModel.objects.create(field={'g': '002'}),
  123. HStoreModel.objects.create(field={'g': '042'}),
  124. HStoreModel.objects.create(field={'g': '981'}),
  125. )
  126. self.assertSequenceEqual(
  127. HStoreModel.objects.filter(field__has_key='g').order_by('field__g'),
  128. [more_objs[1], more_objs[2], more_objs[0], more_objs[3]]
  129. )
  130. def test_keys_contains(self):
  131. self.assertSequenceEqual(
  132. HStoreModel.objects.filter(field__keys__contains=['a']),
  133. self.objs[:2]
  134. )
  135. def test_values_overlap(self):
  136. self.assertSequenceEqual(
  137. HStoreModel.objects.filter(field__values__overlap=['b', 'd']),
  138. self.objs[:3]
  139. )
  140. def test_key_isnull(self):
  141. obj = HStoreModel.objects.create(field={'a': None})
  142. self.assertSequenceEqual(
  143. HStoreModel.objects.filter(field__a__isnull=True),
  144. self.objs[2:5] + [obj]
  145. )
  146. self.assertSequenceEqual(
  147. HStoreModel.objects.filter(field__a__isnull=False),
  148. self.objs[:2]
  149. )
  150. def test_usage_in_subquery(self):
  151. self.assertSequenceEqual(
  152. HStoreModel.objects.filter(id__in=HStoreModel.objects.filter(field__a='b')),
  153. self.objs[:2]
  154. )
  155. @isolate_apps('postgres_tests')
  156. class TestChecks(PostgreSQLSimpleTestCase):
  157. def test_invalid_default(self):
  158. class MyModel(PostgreSQLModel):
  159. field = HStoreField(default={})
  160. model = MyModel()
  161. self.assertEqual(model.check(), [
  162. checks.Warning(
  163. msg=(
  164. "HStoreField default should be a callable instead of an "
  165. "instance so that it's not shared between all field "
  166. "instances."
  167. ),
  168. hint='Use a callable instead, e.g., use `dict` instead of `{}`.',
  169. obj=MyModel._meta.get_field('field'),
  170. id='postgres.E003',
  171. )
  172. ])
  173. def test_valid_default(self):
  174. class MyModel(PostgreSQLModel):
  175. field = HStoreField(default=dict)
  176. self.assertEqual(MyModel().check(), [])
  177. class TestSerialization(PostgreSQLSimpleTestCase):
  178. test_data = json.dumps([{
  179. 'model': 'postgres_tests.hstoremodel',
  180. 'pk': None,
  181. 'fields': {
  182. 'field': json.dumps({'a': 'b'}),
  183. 'array_field': json.dumps([
  184. json.dumps({'a': 'b'}),
  185. json.dumps({'b': 'a'}),
  186. ]),
  187. },
  188. }])
  189. def test_dumping(self):
  190. instance = HStoreModel(field={'a': 'b'}, array_field=[{'a': 'b'}, {'b': 'a'}])
  191. data = serializers.serialize('json', [instance])
  192. self.assertEqual(json.loads(data), json.loads(self.test_data))
  193. def test_loading(self):
  194. instance = list(serializers.deserialize('json', self.test_data))[0].object
  195. self.assertEqual(instance.field, {'a': 'b'})
  196. self.assertEqual(instance.array_field, [{'a': 'b'}, {'b': 'a'}])
  197. def test_roundtrip_with_null(self):
  198. instance = HStoreModel(field={'a': 'b', 'c': None})
  199. data = serializers.serialize('json', [instance])
  200. new_instance = list(serializers.deserialize('json', data))[0].object
  201. self.assertEqual(instance.field, new_instance.field)
  202. class TestValidation(PostgreSQLSimpleTestCase):
  203. def test_not_a_string(self):
  204. field = HStoreField()
  205. with self.assertRaises(exceptions.ValidationError) as cm:
  206. field.clean({'a': 1}, None)
  207. self.assertEqual(cm.exception.code, 'not_a_string')
  208. self.assertEqual(cm.exception.message % cm.exception.params, 'The value of "a" is not a string or null.')
  209. def test_none_allowed_as_value(self):
  210. field = HStoreField()
  211. self.assertEqual(field.clean({'a': None}, None), {'a': None})
  212. class TestFormField(PostgreSQLSimpleTestCase):
  213. def test_valid(self):
  214. field = forms.HStoreField()
  215. value = field.clean('{"a": "b"}')
  216. self.assertEqual(value, {'a': 'b'})
  217. def test_invalid_json(self):
  218. field = forms.HStoreField()
  219. with self.assertRaises(exceptions.ValidationError) as cm:
  220. field.clean('{"a": "b"')
  221. self.assertEqual(cm.exception.messages[0], 'Could not load JSON data.')
  222. self.assertEqual(cm.exception.code, 'invalid_json')
  223. def test_non_dict_json(self):
  224. field = forms.HStoreField()
  225. msg = 'Input must be a JSON dictionary.'
  226. with self.assertRaisesMessage(exceptions.ValidationError, msg) as cm:
  227. field.clean('["a", "b", 1]')
  228. self.assertEqual(cm.exception.code, 'invalid_format')
  229. def test_not_string_values(self):
  230. field = forms.HStoreField()
  231. value = field.clean('{"a": 1}')
  232. self.assertEqual(value, {'a': '1'})
  233. def test_none_value(self):
  234. field = forms.HStoreField()
  235. value = field.clean('{"a": null}')
  236. self.assertEqual(value, {'a': None})
  237. def test_empty(self):
  238. field = forms.HStoreField(required=False)
  239. value = field.clean('')
  240. self.assertEqual(value, {})
  241. def test_model_field_formfield(self):
  242. model_field = HStoreField()
  243. form_field = model_field.formfield()
  244. self.assertIsInstance(form_field, forms.HStoreField)
  245. def test_field_has_changed(self):
  246. class HStoreFormTest(Form):
  247. f1 = forms.HStoreField()
  248. form_w_hstore = HStoreFormTest()
  249. self.assertFalse(form_w_hstore.has_changed())
  250. form_w_hstore = HStoreFormTest({'f1': '{"a": 1}'})
  251. self.assertTrue(form_w_hstore.has_changed())
  252. form_w_hstore = HStoreFormTest({'f1': '{"a": 1}'}, initial={'f1': '{"a": 1}'})
  253. self.assertFalse(form_w_hstore.has_changed())
  254. form_w_hstore = HStoreFormTest({'f1': '{"a": 2}'}, initial={'f1': '{"a": 1}'})
  255. self.assertTrue(form_w_hstore.has_changed())
  256. form_w_hstore = HStoreFormTest({'f1': '{"a": 1}'}, initial={'f1': {"a": 1}})
  257. self.assertFalse(form_w_hstore.has_changed())
  258. form_w_hstore = HStoreFormTest({'f1': '{"a": 2}'}, initial={'f1': {"a": 1}})
  259. self.assertTrue(form_w_hstore.has_changed())
  260. class TestValidator(PostgreSQLSimpleTestCase):
  261. def test_simple_valid(self):
  262. validator = KeysValidator(keys=['a', 'b'])
  263. validator({'a': 'foo', 'b': 'bar', 'c': 'baz'})
  264. def test_missing_keys(self):
  265. validator = KeysValidator(keys=['a', 'b'])
  266. with self.assertRaises(exceptions.ValidationError) as cm:
  267. validator({'a': 'foo', 'c': 'baz'})
  268. self.assertEqual(cm.exception.messages[0], 'Some keys were missing: b')
  269. self.assertEqual(cm.exception.code, 'missing_keys')
  270. def test_strict_valid(self):
  271. validator = KeysValidator(keys=['a', 'b'], strict=True)
  272. validator({'a': 'foo', 'b': 'bar'})
  273. def test_extra_keys(self):
  274. validator = KeysValidator(keys=['a', 'b'], strict=True)
  275. with self.assertRaises(exceptions.ValidationError) as cm:
  276. validator({'a': 'foo', 'b': 'bar', 'c': 'baz'})
  277. self.assertEqual(cm.exception.messages[0], 'Some unknown keys were provided: c')
  278. self.assertEqual(cm.exception.code, 'extra_keys')
  279. def test_custom_messages(self):
  280. messages = {
  281. 'missing_keys': 'Foobar',
  282. }
  283. validator = KeysValidator(keys=['a', 'b'], strict=True, messages=messages)
  284. with self.assertRaises(exceptions.ValidationError) as cm:
  285. validator({'a': 'foo', 'c': 'baz'})
  286. self.assertEqual(cm.exception.messages[0], 'Foobar')
  287. self.assertEqual(cm.exception.code, 'missing_keys')
  288. with self.assertRaises(exceptions.ValidationError) as cm:
  289. validator({'a': 'foo', 'b': 'bar', 'c': 'baz'})
  290. self.assertEqual(cm.exception.messages[0], 'Some unknown keys were provided: c')
  291. self.assertEqual(cm.exception.code, 'extra_keys')
  292. def test_deconstruct(self):
  293. messages = {
  294. 'missing_keys': 'Foobar',
  295. }
  296. validator = KeysValidator(keys=['a', 'b'], strict=True, messages=messages)
  297. path, args, kwargs = validator.deconstruct()
  298. self.assertEqual(path, 'django.contrib.postgres.validators.KeysValidator')
  299. self.assertEqual(args, ())
  300. self.assertEqual(kwargs, {'keys': ['a', 'b'], 'strict': True, 'messages': messages})