Use model method as default value for Django Model?

Question:

I actually have this method in my Model:

def speed_score_compute(self):
  # Speed score:                                                                                                     
  #                                                                                                                  
  #   - 8 point for every % of time spent in                                                                         
  #     high intensity running phase.                                                                                
  #   - Average Speed in High Intensity                                                                              
  #     running phase (30km/h = 50 points                                                                            
  #     0-15km/h = 15 points )

  try:
    high_intensity_running_ratio = ((
      (self.h_i_run_time * 100)/self.training_length) * 8)
  except ZeroDivisionError:
    return 0
  high_intensity_running_ratio = min(50, high_intensity_running_ratio)
  if self.h_i_average_speed < 15:
    average_speed_score = 10
  else:
    average_speed_score = self.cross_multiplication(
      30, self.h_i_average_speed, 50)
  final_speed_score = high_intensity_running_ratio + average_speed_score
  return final_speed_score

I want to use it as default for my Model like this:

speed_score = models.IntegerField(default=speed_score_compute)

But this don’t work (see Error message below) . I’ve checked different topic like this one, but this work only for function (not using self attribute) but not for methods (I must use methods since I’m working with actual object attributes).

Django doc. seems to talk about this but I don’t get it clearly maybe because I’m still a newbie to Django and Programmation in general.

Is there a way to achieve this ?

EDIT:

My function is defined above my models. But here is my error message:

ValueError: Could not find function speed_score_compute in tournament.models.
Please note that due to Python 2 limitations, you cannot serialize unbound method functions (e.g. a method declared and used in the same class body). Please move the function into the main module body to use migrations.
For more information, see https://docs.djangoproject.com/en/1.8/topics/migrations/#serializing-values

Error message is clear, it seems that I’m not able to do this. But is there another way to achieve this ?

Asked By: Sami Boudoukha

||

Answers:

The problem

When we provide a default=callable and provide a method from a model, it doesn’t get called with the self argument that is the model instance.

Overriding save()

I haven’t found a better solution than to override MyModel.save()* like below:

class MyModel(models.Model):
    def save(self, *args, **kwargs):
        if self.speed_score is None:
            self.speed_score = ...
            # or
            self.calculate_speed_score()

        # Now we call the actual save method
        super(MyModel, self).save(*args, **kwargs)

This makes it so that if you try to save your model, without a set value for that field, it is populated before the save.

Personally I just prefer this approach of having everything that belongs to a model, defined in the model (data, methods, validation, default values etc). I think this approach is referred to as the fat Django model approach.

*If you find a better approach, I’d like to learn about it too!

Using a pre_save signal

Django provides a pre_save signal that runs before save() is ran on the model. Signals run synchronously, i.e. the pre_save code needs to finish running before save() is called on the model. You’ll get the same results (and order of execution) as overriding the save().

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel


@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    instance = kwargs['instance']
    instance.populate_default_values()

If you prefer to keep the default values behavior separated from the model, this approach is for you!

When is save() called? Can I work with the object before it gets saved?

Good questioin! Because we’d like the ability to work with our object before subjecting to saving of populating default values.

To get an object, without saving it, just to work with it we can do:

instance = MyModel()

If you create it using MyModel.objects.create(), then save() will be called. It is essentially (see source code) equivalent to:

instance = MyModel()
instance.save()

If it’s interesting to you, you can also define a MyModel.populate_default_values(), that you can call at any stage of the object lifecycle (at creation, at save, or on-demande, it’s up to you)

Answered By: bakkal

I think you should try data migration. Sharing below links for reference:

Data migrations in Django 101

Django migrations RunPython not able to call model methods

Answered By: Aman