The first place I would look is django HStoreField
docs. Finding nothing relevant there I'd assume that such feature, if it exists, is not implemented by Django.
Thus, my search are now aiming at PostgreSQL hstore
docs. After browsing this document, I can't find any function which purpose is clearly to update a specific key of a hstore. So I check each function in details to figure out if any could be used for such purpose.
hstore || hstore → hstore
Concatenates two hstores.
'a=>b, c=>d'::hstore || 'c=>x, d=>q'::hstore → "a"=>"b", "c"=>"x", "d"=>"q"
That's a bingo! Using the ||
operator, we can do something like UPDATE "a" SET "data" = "a"."data" || hstore('existing_key=>new_value')
.
Now, as it's not implemented by Django, let's implement it ourselves:
import re
from django.contrib.postgres.fields import HStoreField
from django.db.models import Func, Value
def hstore_def_escape(s):
return re.sub(r'([\\"])', r'\\\1', s)
class HStore(Func)
function = 'hstore'
output_field = HStoreField()
def __init__(self, expression, **kwargs):
if isinstance(expression, dict):
expression = Value(
','.join(
'"{}" => "{}"'.format(hstore_def_escape(k), hstore_def_escape(v))
for k, v in expression.items()
)
)
super().__init__(expression, **kwargs)
class ConcatHStore(Func):
arg_joiner = ' || '
template = '%(expressions)s'
output_field = HStoreField()
def __init__(self, *expressions, **kwargs):
expressions = [
HStore(e) if isinstance(e, dict) else e
for e in expressions
]
super().__init__(*expressions, **kwargs)
Now you can do:
from django.db.models import F
Result.objects.update(data=ConcatHStore(F('data'), {'existing_key': 'new_value'}))