0

I want to store a dict in Django. a very basic solution would be this:

class foo(models.Model):
    bar = models.TextField()

fooInstance.bar = json.dumps(my_dict)

but I wanted to be able to access it directly and not constantly use json, so I did the following:

class foo(models.Model):
    _bar = models.TextField()

    @property
    def bar(self):
        import json
        return json.loads(self._bar)

    @bar.setter
    def bar(self, new_bar):
        import json
        self._bar = json.dumps(new_bar)

fooInstance.bar = my_dict

and this works for the most part, only that I can't do one thing:

fooInstance.bar['key'] = 'value'

this expression goes through, no error. but the value is not being set. does anybody has any ideas on how this could be done?

Any help is appreciated.

Yotam Alon
  • 138
  • 1
  • 11

2 Answers2

0

Although it is better to use a JSONField to handle it for you, but I will answer your question as-is not to enforce a specific DB backend.

The root cause of problem is that everytime that you call bar on your model instance, a new dictionary is created and returned, so your changes will be lost.

You should store the created dictionary in a property to avoid this re-creation behaviour.

class foo(models.Model):
    _bar = models.TextField()

    def get_bar(self):
        dict_value = getattr(self, '_bar_dict', None)
        if not dict_value:
            import json
            dict_value = json.loads(self._bar)
            setattr(self, '_bar_dict', dict_value)
        return dict_value

    def set_bar(self, new_bar):
        import json
        self._bar = json.dumps(new_bar)
        self._bar_dict = dict(new_bar)

    bar = property(get_bar, set_bar)

Or you may use @cached_property decorator. [see 1 and 2]

Mohammad Jafar Mashhadi
  • 4,102
  • 3
  • 29
  • 49
  • I am not sure this will do what I need. the issue is that doing fooInstance.save() does not save the new value. also, the setter is not being invoked. I added a print statement in the setter, and did not see the print when tried it. – Yotam Alon Oct 16 '17 at 12:07
  • @YotamAlon Print statements in the property setter methods will be suppressed (i.e. no output will be created, but the code will run) NEVER DEBUG WITH PRINT STATEMENTS. The code is correct. – Mohammad Jafar Mashhadi Oct 16 '17 at 12:43
  • While I understand the paradigm of avoiding prints, the fact is that when I used the setter properly (fooInstance.bar = new_bar) it showed me the print. regardless, after testing, this does not work. doing fooInstance.bar[1] = 2, and fooInstance.save() does not save the value to the db. – Yotam Alon Oct 19 '17 at 11:54
0

So instead of updating _bar on every change to bar, it is acceptable to change _bar only before saving. here is my fix:

class foo(models.Model)
    _bar = models.TextField()

        def __getattr__(self, attr):
            if attr == 'bar':
                import json
                try:
                    self.bar = json.loads(self._bar)
                except ValueError:
                    return {}
                return self.bar
            else:
                return super(foo, self).__getattr__(self, attr)

        def save(self):
            import json
            if hasattr(self, bar):
                self._bar = json.dumps(self.bar)
            super(foo, self).save()

this works. I am able to both work with fooInstance.bar as a regular dict, and save it to the db.

Yotam Alon
  • 138
  • 1
  • 11