Reputation: 23
I have a Django model with a HStoreField and I'm trying to update a specific key on the data in this field. I'm trying not to load the model into memory as there's a fair bit of data and it would be for a lot of object instances.
I have my Result object which has 'data' field and I'm trying to update an 'existingkey', I've tried:
Result.objects.update(data__existingkey='new_value')
But I just get FieldDoesNotExist: ResultsContainer has no field named 'data__existingkey'
I thought this would work as Result.objects.filter(data__existingkey='value')
works fine. Any suggestions would be appreciated many thanks
Upvotes: 2
Views: 413
Reputation: 35012
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'}))
Upvotes: 1