Mongoengine creation_time attribute in Document
Asked Answered
L

9

34

I am trying to add a creation_time attribute to my documents. The following would be an example:

import datetime

class MyModel(mongoengine.Document):
    creation_date = mongo.DateTimeField()
    modified_date = mongo.DateTimeField(default=datetime.datetime.now)

Django models have built in parameter for their DateTimeField objects like add_now, etc., but MongoEngine does not support this.

I am wondering if best way to do this is the following:

m,created = MyModel.objects.get_or_create()
if created:
    m.creation_date = datetime.datetime.now()

or if there is a better, nicer way.

Louannlouanna answered 11/11, 2011 at 18:7 Comment(0)
T
77

You could override the save method.

class MyModel(mongoengine.Document):
    creation_date = mongo.DateTimeField()
    modified_date = mongo.DateTimeField(default=datetime.datetime.now)

    def save(self, *args, **kwargs):
        if not self.creation_date:
            self.creation_date = datetime.datetime.now()
        self.modified_date = datetime.datetime.now()
        return super(MyModel, self).save(*args, **kwargs)
Tc answered 15/11, 2011 at 21:34 Comment(4)
This is exactly what I needed. I had figured out the default bit, but overriding the save method to track the modified time is perfect. Thank you :)Ertha
The problem with this though is that the save function won't be called if you do an update instead of a .save right?Turmeric
override clean method instead of save should be better, docs hereDib
The modified_date can't be updated when I use the function updateBazaar
C
28

As an aside, the creation time is stamped into the _id attribute - if you do:

YourObject.id.generation_time

Will give you a datetime stamp.

Contort answered 17/7, 2012 at 6:43 Comment(1)
This is better than the accepted answer. I've used @property on a function like this to get the created_time: def created_at(self): return self.id.generation_time if self.id else NoneField
M
6

One nice solution is reusing a single signal handler for multiple documents.

class User(Document):
    # other fields...
    created_at = DateTimeField(required=True, default=datetime.utcnow)
    updated_at = DateTimeField(required=True)

class Post(Document):
    # other fields...
    created_at = DateTimeField(required=True, default=datetime.utcnow)
    updated_at = DateTimeField(required=True)

def update_timestamp(sender, document, **kwargs):
    document.updated_at = datetime.utcnow()

signals.pre_save.connect(update_timestamp, sender=User)
signals.pre_save.connect(update_timestamp, sender=Post)

Be careful to assign a callable and not a fixed-value as the default, for example default=datetime.utcnow without (). Some of the other answers on this page are incorrect and would cause created_at for new documents to always be set to the time your app was first loaded.

It's also always better to store UTC dates (datetime.utcnow instead of datetime.now) in your database.

Minstrelsy answered 9/1, 2013 at 21:49 Comment(0)
C
5

If you are using the timestamp field in a bunch of Documents you can keep your code DRY by creating an abstract Document instead.

from datetime import datetime
from mongoengine import Document

class CreateUpdateDocument(Document):
    meta = {
        'abstract': True
    }

    # last updated timestamp
    updated_at = DateTimeField(default=datetime.now)

    # timestamp of when entry was created
    created_at = DateTimeField(default=datetime.now)

    def save(self, *args, **kwargs):
        if not self.created_at:
            self.created_at = datetime.now()
        self.updated_at = datetime.now()
        return super(CreateUpdateDocument, self).save(*args, **kwargs)
Catron answered 2/10, 2014 at 11:17 Comment(1)
You might want to put self.updated_at = datetime.now() under an else statement, so you can keep the idea of "if the updated_at is none, that means the model was never updated".Oliviaolivie
C
4
# -*- coding: utf-8 -*-
from mongoengine import *
from mongoengine import signals
from datetime import datetime

class User(Document):
    email = StringField(required=True, unique=True)
    first_name = StringField(max_length=50)
    last_name = StringField(max_length=50)
    # audit fields
    created_on = DateTimeField(default=datetime.now())
    updated_on = DateTimeField(default=datetime.now())

    @classmethod
    def pre_save(cls, sender, document, **kwargs):
        document.updated_on = datetime.now()

signals.pre_save.connect(User.pre_save, sender=User)
Characterize answered 7/8, 2012 at 18:19 Comment(0)
M
4

My preferred solution is to use the @property decorator to return the creation datetime as extracted from the ObjectId:

@property
def creation_stamp(self):
    return self.id.generation_time
Monoplane answered 7/11, 2014 at 14:49 Comment(0)
B
1

Try use lambda value:

import datetime
from mongoengine import Document

class MyModel(Document):
    creation_date = mongo.DateTimeField()
    modified_date = mongo.DateTimeField(default=lambda : datetime.datetime.now())
Batman answered 23/4, 2020 at 11:22 Comment(1)
No need to use a lambda, just use mongo.DateTimeField(default=datetime.datetime.now)Shade
D
0

Traditionally, I've set the creation_date default to datetime.now() and then have hidden the field on the admin form so you remove the possibility of a user overwriting the correct value. That requires almost no code.

Overriding the save method as suggested by Willian is also effective since you can programmtically block any updates to the creation_date and update the modfied_date at the same time.

Digitize answered 18/11, 2011 at 17:34 Comment(1)
when you say "traditionally", do you mean using the Django ORM or MongoEngine specifically?Louannlouanna
S
-8

You could use auto_now_add parameter as per documentation:

class MyModel(mongoengine.Document):
    creation_date = mongo.DateTimeField(auto_now_add = True)
    modified_date = mongo.DateTimeField(auto_now = True)
Stepson answered 18/11, 2011 at 15:12 Comment(1)
That is most unfortunate. Then overriding the save as in the accepted answer is the right answer. I'd create a patch to add the feature though :)Stepson

© 2022 - 2024 — McMap. All rights reserved.