McCree Feng
McCree Feng

Reputation: 219

How to auto-update update time in MongoEngine?

There are some collections that I created using flask-mongoEngine. For example, such as Author, Book, Company. The same part is they have 2 filed named

created_at = db.DateTimeField(required=True, default=utcnow())
updated_at = db.DateTimeField(required=True, default=utcnow())

Everytime I create a new document, I need to update the value of created_at and updated_at , also when I update the document, I need to update the value of updated_at. Are there a good way, when I do save() operation, these time field can auto update?

Upvotes: 4

Views: 3344

Answers (3)

Jose Luis Cruz
Jose Luis Cruz

Reputation: 1

The method that worked for me was first creating a document using datetime.utc, as MongoDB converted the timedate information within the DB to the appropriate timezone values upon retrieval to my client app.

from mongoengine import Document, fields
from datetime import datetime
    
class Example(Document):
    created_at = fields.DateTimeField(required=True, default=datetime.utcnow)
    updated_at = fields.DateTimeField(required=True, default=datetime.utcnow)

Whenever there is a request to update a specific instance of a document, the "updated_at" key is indexed from the data, and is given a new value, that being the current datetime.utcnow(). The specific document is then found from the database via the primary key. After this, just save the changes. I also added some additional lines, when using serializers.

from rest_framework.response import Response
from .models import Example
from .serializers import ExampleSerializer
from datetime import datetime

def updateDocument(request, pk): 
    data = request.data
    data["updated_at"] = datetime.utcnow()
    document = Example.objects.get(id=pk)
    serializer = ExampleSerializer(instance=document, data=data)

    if serializer.is_valid():
        serializer.save()

    return Response(serializer.data)

Upvotes: 0

Charnel
Charnel

Reputation: 4432

Another option is to use inheritance (assuming you would like to have this two fields defined in more then a single model): create a base class with created/updated timestamp fields, override save method and inherite your document class from this base class:

import datetime

class TimestampedDocument(Document):

    meta = {'allow_inheritance': True, 'abstract': True}

    created_at = DateTimeField(required=True, default=datetime.datetime.now)
    updated_at = DateTimeField(required=True, default=datetime.datetime.now)

    def save(
        self,
        force_insert=False,
        validate=True,
        clean=True,
        write_concern=None,
        cascade=None,
        cascade_kwargs=None,
        _refs=None,
        save_condition=None,
        signal_kwargs=None,
        **kwargs,
    ):
        self.updated = datetime.datetime.now()
        super().save(
            force_insert, validate, clean, write_concern, cascade, cascade_kwargs, _refs, save_condition,
            signal_kwargs, **kwargs
        )

...

class YourDocument(TimestampedDocument):
    meta = {'collection': 'YourDocument'}

In this case created_at is set one's when document is created and updated_at updates each time you modify the doc.

Upvotes: 4

pythko
pythko

Reputation: 124

Setting the defaults to be utcnow() with the parentheses causes Python to execute the utcnow function when the class is created, not when a new object using that class is created. Instead, you should set the default to be the function without the (). That will pass the function as an object itself instead of calling it right away, and when a new object is created from this class, the function will execute.

Also, the Python docs recommend using datetime.now over utcnow. Going with that, your example should look like this:

import datetime

class Example(mongoengine.Document):
    created_at = db.DateTimeField(required=True, default=datetime.datetime.now)
    updated_at = db.DateTimeField(required=True, default=datetime.datetime.now)

As for updating the updated_at attribute whenever you call save(), you can create a wrapper function for save() that will set updated_at to datetime.now(), then save. Then, instead of calling save(), call your wrapper function whenever you want to save.

A wrapper function is a function that does a little preprocessing and then calls another function (see wikipedia: https://en.wikipedia.org/wiki/Wrapper_function).

For example, you write a function like this:

def my_save(object):
    object.updated_at = datetime.datetime.now()
    return object.save()

And then whenever you want to save an object, you call my_save() instead of object.save().

Upvotes: 5

Related Questions