2

I have a question on how to set model attributes dynamically when model instances are initiated.

I am using a simple model with a native PostgreSQL JSONField:

from django.db import models
from django.contrib.postgres.fields import JSONField

class Booking(models.Model):
    data = JSONField(blank=False, encoder=DjangoJSONEncoder)

Is there any way to set attributes on the model instance based on the values stored in the 'data' field when the model is instantiated?

I want to be able to do this:

from .models import Booking

b = Booking.objects.create(data={'foo':'bar'})
b.foo # this should yield 'bar'

My initial thought was to override the model's init method and set the instance attributes with setattr(), but overriding a model's init method is strongly discouraged in the Django docs.

Is there any other way to achieve this? Any help is appreciated.

PS: I know that I can access the values stored in 'data' on a model instance like so: booking.data['foo'], so this is not what I am looking for.

nikolas-berlin
  • 1,080
  • 8
  • 6

2 Answers2

1

You could simply implement the __getattr__ hook in your model, ie:

class Booking(models.Model):

    # ...
    def __getattr__(self, name):
        try:
            return self.data[name]
        except KeyError:
            try:
                return super(Boooking, self).__getattr__(name)
            except AttributeError: 
                raise AttributeError(
                   "object %s has no attribute '%s'" % (type(self).__name__, name)
                   ) 

but unless you are really confident that your json content will always have the same structure I would advise against it - using the plain instance.data[key] syntax is more explicit.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thanks to all. __getattr__ was the missing piece I was overlooking. Accepting bruno desthuilliers's answer because the error handling is more complete. By the way, I agree: instance.data[key] is more explicit, but in my usecase the instance is passed to a third party class method which calls getattr(instance, 'foo'), and overriding this method appears less clean to me. – nikolas-berlin May 10 '17 at 11:42
0

If you really want to do it, you can override getattr method.

class Booking(models.Model):

    def __getattr__(self, attr):
        if attr in self.data:
            return self.data[attr]
        else:
            return super(Booking, self).__getattr__(attr)
hspandher
  • 15,934
  • 2
  • 32
  • 45