10

I have a model that requires some post-processing (I generate an MD5 of the body field).

models.py

class MyModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    body = models.TextField()
    md5 = models.CharField(max_length=32)
    ...

    def save(self, *args, **kwargs):
        if self.pk is None: # Only for create (edit disabled)
            self.md5 = get_md5(self.body)
            super(MyModel, self).save(*args, **kwargs)

The problem is that the final block won't execute because I don't see a way to check if the instance is new or not: self.pk is never None because a UUID is populated before saving.

I'd like to know what the best practice is for handling this.

Thanks in advance.

Update:

The only solution I can think of is to call the database directly and:

  1. Check if the id exists
  2. Compare the modified and created fields to tell if it's an edit
Daniel van Flymen
  • 10,931
  • 4
  • 24
  • 39
  • So, `self.md5` is `None` for a new instance, right? Why not use `self.md5` in place of `self.pk` then? – xyres Dec 15 '15 at 07:33
  • I could do that, but there's also a few other things that happen (another model gets created with an FK back to this one). Unfortunately, I neglected to add that to this trivial example :/ – Daniel van Flymen Dec 15 '15 at 07:35
  • 1
    As possible solutions you can try to use post-save signals. It contains boolean flag - created. But this approach will add additional query to db. – Alex Lisovoy Dec 15 '15 at 07:53

4 Answers4

5

EDIT

self.pk is never None because a UUID is populated before saving.

Instead of setting a default for id, use a method to set id for the new instance.

class MyModel(...):
    id = models.UUIDField(primary_key=True, default=None,...)

    def set_pk(self):
        self.pk = uuid.uuid4()

    def save(self, *args, **kwargs):
        if self.pk is None:
            self.set_pk()
            self.md5 = get_md5(self.body)
            super(MyModel, self).save(*args, **kwargs)
xyres
  • 20,487
  • 3
  • 56
  • 85
  • I don't think you understood my question, how will this help? – Daniel van Flymen Dec 15 '15 at 07:26
  • I appreciate the help, there's no way I can see (without calling the DB) to tell if the object is new or not. – Daniel van Flymen Dec 15 '15 at 07:31
  • Thanks for this @xyres. It's a solution but I'm putting common functionality like `id = models.UUIDField...` in a common model that I'm inheriting from on all my models, would I have to then override save on all of them? – Daniel van Flymen Dec 15 '15 at 08:15
  • @DanielvanFlymen Just put the `save()` and `set_key()` methods in the parent model. The children models will inherit these methods. – xyres Dec 15 '15 at 09:45
4

Looks like the cleanest approach to this is to make sure that all your models have a created date on them by inheriting from an Abstract model, then you simply check if created has a value:

models.py

class BaseModel(models.Model):
    """
    Base model which all other models can inherit from.
    """
    id = fields.CustomUUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        # Abstract models are not created in the DB
        abstract = True

class MyModel(BaseModel):
    my_field = models.CharField(max_length=50)

    def save(self, *args, **kwargs):
        if self.created:
            # Do stuff
            pass
        super(MyModel, self).save(*args, **kwargs)
Daniel van Flymen
  • 10,931
  • 4
  • 24
  • 39
3

I just got the same issue in my project and found out that you can check the internal state of the model's instance:

def save(self, *args, **kwargs):
    if self._state.adding: # Only for create (edit disabled)
        self.md5 = get_md5(self.body)
        super(MyModel, self).save(*args, **kwargs)

But this solution relies on internal implementation and may stop working after Django is updated

1

As I've answered here as well, the cleanest solution I've found that doesn't require any additional datetime fields or similar tinkering is to plug into the Django's post_save signal. Add this to your models.py:

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

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance
        self.md5 = get_md5(self.body)

This callback will block the save method, so you can do things like trigger notifications or update the model further before your response is sent back over the wire, whether you're using forms or the Django REST framework for AJAX calls. Of course, use responsibly and offload heavy tasks to a job queue instead of keeping your users waiting :)

Community
  • 1
  • 1
metakermit
  • 21,267
  • 15
  • 86
  • 95