gareth71
gareth71

Reputation: 23

How can I update a specific key on a HStoreField in django?

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

Answers (1)

Antoine Pinsard
Antoine Pinsard

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

Related Questions