guettli
guettli

Reputation: 27107

Django Models: default callback: missing "self"

According to the docs, if you give a model field a callable as default, then this default method gets no parameters:

https://docs.djangoproject.com/en/1.8/ref/models/fields/#default

def contact_default():
    return {"email": "[email protected]"}

contact_info = JSONField("ContactInfo", default=contact_default)

I am missing the access to the other attributes of the instance. At best I would like to access self.

Use case:

class Face(models.Model):
    male=models.BooleanField()
    beard=models.NullBooleanField()

If the face is a not male, then beard should be set to False.

The default value should be applied only for new instances.

Upvotes: 4

Views: 1007

Answers (6)

Régis B.
Régis B.

Reputation: 10618

To answer your question: it's not possible to read the attributes of the model instance being created in the default function. pre_save signals will be run every time an instance is saved, so you don't want to do that either. There are ways around that, but signals make the code harder to read (IMHO).

What you might want to do is add a convenient method to the manager of the model manager.

class FaceManager(models.Manager):
    def create_person(male, beard=None):
        if not male and beard is None:
            return self.create(male=False, beard=False)
        else:
            return self.create(male=male, beard=beard)

class Face(models.Model):
    objects = FaceManager()

You can then create a non-bearded, non-female face:

Face.objects.create_person(male=False)

Upvotes: 1

Ryan Loechner
Ryan Loechner

Reputation: 119

I would override save.

def save(self):
    if self.male:
        self.beard = True
        super(Face, self).save()

Upvotes: 0

Sachin Gupta
Sachin Gupta

Reputation: 404

You can't use self in the function for defining default values. You would have to use pre_save signal for this.

from django.db.models.signals import post_save, pre_save

@receiver(pre_save, sender=Face)
def check_beard(sender, instance=None, created=False, **kwargs):
    if not instance.id and not instance.male: # Check instance id if it saving for first time, and set default
        instance.beard = False

Upvotes: 2

lehins
lehins

Reputation: 9767

You can use django-smartfields for that:

from django.db import models 
from smartfields import fields
from smartfields.dependencies import Dependency

def has_beard(is_male, instance=instance, **kwargs):
    if not is_male:
        return False

class Face(models.Model):
    male=fields.BooleanField(dependencies=[
        Dependency(attname='beard', default=has_beard
    ])
    beard=models.NullBooleanField()

This will basically act as a default on the beard field, except you get access to male field value and a model instance. If you would like to have this sort of check every time male field is changed than you can extend smartfields.processors.BaseProcessor and pass it as a processor keyword argument to a Dependency.

In case your actual problem, you can do it this way:

def contact_default(value, instance=instance, **kwargs):
    return {"email": "[email protected]"}

class MyModel(models.Model):
    # ... other fields
    contact_info = fields.JSONField("ContactInfo", dependencies=[ 
        Dependency(default=contact_default)
    ])

So, in this example it's a self dependency, so whenever contact_info is null it will call the default function.

Upvotes: 0

Filip Dupanović
Filip Dupanović

Reputation: 33690

There are several approaches you can choose from to interject and set the value of a model field that depends on another.

The suggested approach is to actually use Form.clean(). This would allow you to encapsulate business logic at it's the source.

Another approach would be to override Model.save() or register a handler for django.db.models.signals.pre_save(), though these are usually reserved for overarching relational constraints and application requirements.

Unless your creating the models programmatically, without user input which would otherwise be drained through a ModelForm, you'll have to resort to one of the latter two. In most cases, the obvious approach is to satisfy such requirements through Form.clean().

Upvotes: 0

GwynBleidD
GwynBleidD

Reputation: 20569

You can't access self in default callback, because it is called on creating object and it can't be provided.

But you can override __init__ or save method and check here values of your fields to make some changes. Make sure that in __init__ you will make changes after calling __init__ from parent class.

Upvotes: 0

Related Questions