137

I have a model that I would like to contain a subjects name and their initials (he data is somewhat anonymized and tracked by initials).

Right now, I wrote

class Subject(models.Model):

    name = models.CharField("Name", max_length=30)
    def subject_initials(self):
        return ''.join(map(lambda x: '' if len(x)==0 else x[0],
                           self.name.split(' ')))
    # Next line is what I want to do (or something equivalent), but doesn't work with
    # NameError: name 'self' is not defined
    subject_init = models.CharField("Subject Initials", max_length=5, default=self.subject_initials)

As indicated by the last line, I would prefer to be able to have the initials actually get stored in the database as a field (independent of name), but that is initialized with a default value based on the name field. However, I am having issues as django models don't seem to have a 'self'.

If I change the line to subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials), I can do the syncdb, but can't create new subjects.

Is this possible in Django, having a callable function give a default to some field based on the value of another field?

(For the curious, the reason I want to separate my store initials separately is in rare cases where weird last names may have different than the ones I am tracking. E.g., someone else decided that Subject 1 Named "John O'Mallory" initials are "JM" rather than "JO" and wants to fix edit it as an administrator.)

4 Answers 4

129

Models certainly do have a "self"! It's just that you're trying to define an attribute of a model class as being dependent upon a model instance; that's not possible, as the instance does not (and cannot) exist before your define the class and its attributes.

To get the effect you want, override the save() method of the model class. Make any changes you want to the instance necessary, then call the superclass's method to do the actual saving. Here's a quick example.

def save(self, *args, **kwargs):
    if not self.subject_init:
        self.subject_init = self.subject_initials()
    super(Subject, self).save(*args, **kwargs)

This is covered in Overriding Model Methods in the documentation.

5
  • 15
    Note that in Python 3 you can simply call super().save(*args, **kwargs) (without the Subject, self arguments) as in the example in the referenced documentation.
    – Kurt Peek
    Commented Jun 27, 2018 at 23:01
  • 3
    Too bad this doesn't allow to define a default model attribute value based on another, which is particularly useful in the Admin side (for instance, for an autoincremented field), since it handles it when saving it. Or am I misunderstanding it? Commented Jul 31, 2018 at 16:46
  • If you're adding the field to a model with existing data, it may be necessary to update the existing records using a data migration.
    – djvg
    Commented Aug 30, 2021 at 10:55
  • Note that you'll probably need to allow blank values in your model (blank=True) as serializers will fail to validate when you add the field value in the overwritten save method.
    – 5hizzle
    Commented Oct 11, 2022 at 18:21
  • Correct me if I'm wrong, but I believe the Model.save() method isn't called when you use something like Model.bulk_create(), so I'm not sure how robust this answer is. Commented Aug 17, 2023 at 18:34
19

I don't know if there is a better way of doing this, but you can use a signal handler for the pre_save signal:

from django.db.models.signals import pre_save

def default_subject(sender, instance, using):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

pre_save.connect(default_subject, sender=Subject)
4
  • 1
    You imported post_save instead of pre_save. Commented Jun 19, 2015 at 9:07
  • 1
    @arkocal: thanks for the heads up, just fixed it. You can suggest edits yourself in such cases, it helps fix stuff like this :) Commented Jun 19, 2015 at 9:08
  • 1
    It seems like this implementation is missing the **kwargs argument which all receiver functions should have according to docs.djangoproject.com/en/2.0/topics/signals/…?
    – Kurt Peek
    Commented Jun 27, 2018 at 22:20
  • 2
    The documentation recommends against signals for code paths you control. The accepted answer of overriding save(), perhaps changed to overriding __init__, is better as it's more explicit. Commented Feb 10, 2020 at 10:06
12

Using Django signals, this can be done quite early, by receiving the post_init signal from the model.

from django.db import models
import django.dispatch

class LoremIpsum(models.Model):
    name = models.CharField(
        "Name",
        max_length=30,
    )
    subject_initials = models.CharField(
        "Subject Initials",
        max_length=5,
    )

@django.dispatch.receiver(models.signals.post_init, sender=LoremIpsum)
def set_default_loremipsum_initials(sender, instance, *args, **kwargs):
    """
    Set the default value for `subject_initials` on the `instance`.

    :param sender: The `LoremIpsum` class that sent the signal.
    :param instance: The `LoremIpsum` instance that is being
        initialised.
    :return: None.
    """
    if not instance.subject_initials:
        instance.subject_initials = "".join(map(
                (lambda x: x[0] if x else ""),
                instance.name.split(" ")))

The post_init signal is sent by the class once it has done initialisation on the instance. This way, the instance gets a value for name before testing whether its non-nullable fields are set.

2
3

As an alternative implementation of Gabi Purcaru's answer, you can also connect to the pre_save signal using the receiver decorator:

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


@receiver(pre_save, sender=Subject)
def default_subject(sender, instance, **kwargs):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

This receiver function also takes the **kwargs wildcard keyword arguments which all signal handlers must take according to https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions.

Not the answer you're looking for? Browse other questions tagged or ask your own question.