27

How can I make default value for a field to be taken from existing objects of a model?

I tried these and it didn't worked:

1)

class ModelA(models.Model):
    fieldA = models.CharField(default=self.get_previous())
    
    def get_previous(self):
        return ModelA.objects.all()[0].fieldA

NameError: name 'self' is not defined

2)

class ModelA(models.Model):
    fieldA = models.CharField(default=ModelA.get_previous())

    @staticmethod
    def get_previous():
        return ModelA.objects.all()[0].fieldA

NameError: name 'ModelA' is not defined

3)

class ModelA(models.Model):
    fieldA = models.CharField(default=get_previous())

def get_previous():
    return ModelA.objects.all()[0].fieldA

NameError: global name 'get_previous' is not defined

4)

def get_previous():
    return ModelA.objects.all()[0].fieldA

class ModelA(models.Model):
    fieldA = models.CharField(default=get_previous())

NameError: global name 'ModelA' is not defined

It's clear why 3) and 4) won't work. I can imagine why 1) won't work - looks like class' properies can't refer to instance's (i.e. self). I can imagine why 2) won't work - apparently there's no reference to ModelA until interpreter will go trough whole class.

So how should I approach this?

4 Answers 4

16

In your examples, you need to remove the call operator ().

Currently the statement is executed immediately at the first read-parsing cycle. By specifying the symbol name instead, the Django class receives a function pointer which it will execute when it actually needs the default value.

The example becomes:

def get_previous():
    return ModelA.objects.all()[0].fieldA

class ModelA(models.Model):
    fieldA = models.CharField(default=get_previous)

If you're going to do this for a lot of fields, consider overridding the save function, so you only have to fetch the previous object from the database just once.

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

3 Comments

Good point! In my case though I have many fields to get default value for so I would need separate function for each one. (But I'll consider using lambda to generalize this, something like lambda: get_default('field_name') )
Equivalently, you should be able to do default=lambda: ModelA.objects.all()[0].fieldA.
Be aware that using a lambda function for default will mess with migrations since lambda functions cannot be serialized.
3

If you want to just output default value, override __getattr__() method like this:

class ModelA(models.Model):
  # your fields
  def __getattr__(self, name):
    if(name == 'fieldA' and !self.fieldA):
      return self.get_previous()
    else:
      return super(ModelA, self).__getattr__(name)

Saving default value from object will be little difficultier. First solution that comes in my mind, override save() method (i believe there is simpler solution)

Comments

3

Option 1: Use model forms. These are forms based on models, that you can easily populate their values with values from a specific instance. This option is good if you want the default value as something to be shown to the user in forms, obviously.

Option 2: Override the model's save method, and code the check there - if fieldA is None, then put some value in it. This option is better if you want the default value to work at the data layer, and not only as form default values (ie, also for instances you create in other ways).

1 Comment

My point is to show default value to user, not to save it if there's no value so Option_1 of your answer looks good. Although I like vdboor's answer more as it's python level and not quite django-specific. Djangonauts, correct me if I'm going wrong way ;-)
0

"get_previous" works:

                                  # Here
fieldA = models.CharField(default=get_previous)

But "get_previous()" doesn't work:

                                  # Here
fieldA = models.CharField(default=get_previous())

In addition, your code is lacking Exception Handling so I added it to your code as shown below:

def get_previous():
    try:
        return ModelA.objects.all()[0].fieldA
    except IndexError:
        return None

class ModelA(models.Model):
    fieldA = models.CharField(default=get_previous)

You can also use this code below. first() returns the first object, or None if there are no objects:

def get_previous():
    try:                           # Here
        return ModelA.objects.all().first().fieldA
    except AttributeError:
        return None

class ModelA(models.Model):
    fieldA = models.CharField(default=get_previous)

Comments

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.