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.

+3


source to share


6 answers


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

      

+1


source


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.

0


source


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

.

0


source


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

.

0


source


I would undo the save.

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

      

0


source


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)

      

0


source







All Articles