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.

1
  • Sounds like tying yourself up in knots just for the sake of using JSONField Commented May 10, 2017 at 10:55

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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)

2 Comments

actually it's __getattr__ not getattr, and the parent class may not define it so the super call could raise an AttributeError.
Oops. Typo I guess. Thanks a lot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.