Django models: default callback: missing "self"
As per the docs, if you give a model field to be called by default, then this method has no parameters by default:
https://docs.djangoproject.com/en/1.8/ref/models/fields/#default
def contact_default():
return {"email": "to1@example.com"}
contact_info = JSONField("ContactInfo", default=contact_default)
I am missing access to other instance attributes. At best I would like to access the self
.
Use case:
class Face(models.Model):
male=models.BooleanField()
beard=models.NullBooleanField()
If the face is not masculine, it beard
should be set to False.
The default should only apply to new instances.
source to share
You cannot use self in a function to define default values. To do this, you will need to use the pre_save signal.
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
source to share
You cannot access self in the default callback because it is called when the object is created and it cannot be provided.
But you can override the method __init__
or save
and check your field values here to make some changes. Make sure __init__
you make the changes after the call __init__
from the parent class.
source to share
There are several approaches you can take to enter and set the value of a model field, which depends on another.
The suggested approach is to actually use Form.clean()
. This will allow you to encapsulate your business logic into it by source.
Another approach would be to override Model.save()
or register the handler for django.db.models.signals.pre_save()
, although these are usually reserved for overarching relational constraints and application requirements.
Unless you create models programmatically, without user input that would otherwise merge through ModelForm
, you will have to resort to one of the last two. In most cases, the obvious approach is to satisfy such requirements with Form.clean()
.
source to share
You can use django-smartfields for this:
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()
Basically it will be default
on a field beard
, except that you will be able to access the field value male
and the model instance. If you want to have this type of validation every time a field changes male
than you can expand smartfields.processors.BaseProcessor
and pass it as a keyword argument processor
to Dependency
.
In case of your real problem, you can do it like this:
def contact_default(value, instance=instance, **kwargs):
return {"email": "to1@example.com"}
class MyModel(models.Model):
# ... other fields
contact_info = fields.JSONField("ContactInfo", dependencies=[
Dependency(default=contact_default)
])
So, in this example, it is self-dependent, so whenever contact_info matters null
, it calls the function default
.
source to share
To answer your question: It is not possible to read the attributes of a model instance created in the default function. Signals pre_save
will fire every time the instance is saved, so you don't want to do that either. There are ways to get around this, but signals make the code harder to read (IMHO).
What you can do is add a handy model manager 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()
Then you can create a non-bearded face without a face:
Face.objects.create_person(male=False)
source to share