mart1n
mart1n

Reputation: 6233

Updating timestamps of a model and its related parent with the same time

Let's say I have the following models:

class Blog(TimeStampedModel):
    summary = models.TextField()
    text = models.TextField()

class BlogComment(TimeStampedModel):
    author = models.CharField()
    text = models.CharField()
    blog = models.ForeignKey(Blog, models.CASCADE, related_name='comments')

class BlogTag(TimeStampedModel):
    name = models.CharField()
    blog = models.ForeignKey(Blog, models.CASCADE, related_name='tags')

All three inherit from the following model, which implements some timestamp handling:

class TimeStampedModel(models.Model):
    last_changed = models.DateTimeField()
    created_at = models.DateTimeField(default=timezone.now)

    def save(self, *args, **kwargs):
        try:
            self._meta.get_field('blog')
        except models.FieldDoesNotExist:
            self.last_changed = timezone.now()
            super(TimeStampedModel, self).save(*args, **kwargs)
        else:
            now = timezone.now()
            self.blog.last_changed = now
            self.last_changed = now
            with transaction.atomic():
                super(TimeStampedModel, self).save(*args, **kwargs)
                self.blog.save()

    class Meta:
        abstract = True

The basic idea behind the custom save() is that when for example a comment (instance of BlogComment) is updated, it should update the last_changed timestamp of both the comment instance and the related blog entry.

Unfortunately, the timestamp setting is not perfect since it overrides the blog's timestamp when its own save is called and the times end up being just slightly different:

In [1]: b = Blog.objects.get(id=1)

In [2]: comment0 = t.comments.all()[0]

In [3]: b.last_changed, comment0.last_changed
Out[3]:
(datetime.datetime(2018, 6, 7, 12, 54, 12, 516855, tzinfo=<UTC>),
 datetime.datetime(2018, 6, 7, 10, 22, 09, 201690, tzinfo=<UTC>))

In [4]: comment0.text
Out[4]: 'Some text'

In [5]: comment0.text = 'test'

In [6]: comment0.save()

In [7]: t = blog.objects.get(id=1)

In [8]: comment0 = t.comments.all()[0]

In [9]: t.last_changed, comment0.last_changed
Out[9]:
(datetime.datetime(2018, 6, 7, 13, 25, 58, 64131, tzinfo=<UTC>),
 datetime.datetime(2018, 6, 7, 13, 25, 58, 61960, tzinfo=<UTC>))

Another problem is that the save function relies on the foreign key being named blog, which seems hacky since adding models that uses different foreign key names then requires an update of the save function.

Is there a more general solution to this problem of updating timestamps of related models? How can I amend the save() function above to consistently update models with the same timestamp?

Upvotes: 1

Views: 171

Answers (1)

mart1n
mart1n

Reputation: 6233

Found a solution if using the same foreign key reference in every model:

def save(self, *args, **kwargs):
    self.last_changed = timezone.now()
    try:
        self._meta.get_field('blog')
    except models.FieldDoesNotExist:
        super(TimeStampedModel, self).save(*args, **kwargs)
    else:
        self.blog.last_changed = self.last_changed
        with transaction.atomic():
            super(TimeStampedModel, self).save(*args, **kwargs)
            super(TimeStampedModel, self.blog).save(*args, **kwargs)

Upvotes: 1

Related Questions