Temporarily disable auto_now / auto_now_add

PythonDjangoDatetime

Python Problem Overview


I have a model like this:

class FooBar(models.Model):
    createtime = models.DateTimeField(auto_now_add=True)
    lastupdatetime = models.DateTimeField(auto_now=True)

I want to overwrite the two date fields for some model instances (used when migrating data). The current solution looks like this:

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = False
    elif field.name == "createtime":
        field.auto_now_add = False

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

for field in new_entry._meta.local_fields:
    if field.name == "lastupdatetime":
        field.auto_now = True
    elif field.name == "createtime":
        field.auto_now_add = True

Is there a better solution?

Python Solutions


Solution 1 - Python

I've recently faced this situation while testing my application. I needed to "force" an expired timestamp. In my case, I did the trick by using a queryset update. Like this:

# my model
class FooBar(models.Model):
    title = models.CharField(max_length=255)
    updated_at = models.DateTimeField(auto_now=True, auto_now_add=True)


# my tests
foo = FooBar.objects.get(pk=1)
    
# force a timestamp
lastweek = datetime.datetime.now() - datetime.timedelta(days=7)
FooBar.objects.filter(pk=foo.pk).update(updated_at=lastweek)

# do the testing.

Solution 2 - Python

You can't really disable auto_now/auto_now_add in another way than you already do. If you need the flexibility to change those values, auto_now/auto_now_add is not best choice. It is often more flexible to use default and/or override the save() method to do manipulation right before the object is saved.

Using default and an overridden save() method, one way to solve your problem would be to define your model like this:

class FooBar(models.Model):
    createtime = models.DateTimeField(default=datetime.datetime.now)
    lastupdatetime = models.DateTimeField()
    
    def save(self, *args, **kwargs):
        if not kwargs.pop('skip_lastupdatetime', False):
            self.lastupdatetime = datetime.datetime.now()

        super(FooBar, self).save(*args, **kwargs)

In your code, where you want to skip the automatic lastupdatetime change, just use

new_entry.save(skip_lastupdatetime=True)

If your object is saved in the admin interface or other places, save() will be called without the skip_lastupdatetime argument, and it will behave just as it did before with auto_now.

Solution 3 - Python

You can also use the update_fields parameter of save() and pass your auto_now fields. Here's an example:

# Date you want to force
new_created_date = date(year=2019, month=1, day=1)

# The `created` field is `auto_now` in your model
instance.created = new_created_date
instance.save(update_fields=['created'])

Here's the explanation from Django's documentation: https://docs.djangoproject.com/en/stable/ref/models/instances/#specifying-which-fields-to-save

Solution 4 - Python

I used the suggestion made by the asker, and created some functions. Here is the use case:

turn_off_auto_now(FooBar, "lastupdatetime")
turn_off_auto_now_add(FooBar, "createtime")

new_entry.createtime = date
new_entry.lastupdatetime = date
new_entry.save()

Here's the implementation:

def turn_off_auto_now(ModelClass, field_name):
    def auto_now_off(field):
        field.auto_now = False
    do_to_model(ModelClass, field_name, auto_now_off)
    
def turn_off_auto_now_add(ModelClass, field_name):
    def auto_now_add_off(field):
        field.auto_now_add = False
    do_to_model(ModelClass, field_name, auto_now_add_off)

def do_to_model(ModelClass, field_name, func):
    field = ModelClass._meta.get_field_by_name(field_name)[0]
    func(field)

Similar functions can be created to turn them back on.

Solution 5 - Python

I went the context manager way for reusability.

@contextlib.contextmanager
def suppress_autotime(model, fields):
    _original_values = {}
    for field in model._meta.local_fields:
        if field.name in fields:
            _original_values[field.name] = {
                'auto_now': field.auto_now,
                'auto_now_add': field.auto_now_add,
            }
            field.auto_now = False
            field.auto_now_add = False
    try:
        yield
    finally:
        for field in model._meta.local_fields:
            if field.name in fields:
                field.auto_now = _original_values[field.name]['auto_now']
                field.auto_now_add = _original_values[field.name]['auto_now_add']

Use like so:

with suppress_autotime(my_object, ['updated']):
    my_object.some_field = some_value
    my_object.save()

Boom.

Solution 6 - Python

For those looking at this when they are writing tests, there is a python library called freezegun which allows you to fake the time - so when the auto_now_add code runs, it gets the time you actually want. So:

from datetime import datetime, timedelta
from freezegun import freeze_time

with freeze_time('2016-10-10'):
    new_entry = FooBar.objects.create(...)
with freeze_time('2016-10-17'):
    # use new_entry as you wish, as though it was created 7 days ago
    

It can also be used as a decorator - see the link above for basic docs.

Solution 7 - Python

You can override auto_now_add without special code.

I came across this question when I tried to create an object with particular date:

Post.objects.create(publication_date=date, ...)

where publication_date = models.DateField(auto_now_add=True).

So this is what I've done:

post = Post.objects.create(...)
post.publication_date = date
post.save()

This has successfully overridden auto_now_add.

As a more long-term solution, overriding save method is the way to go: https://code.djangoproject.com/ticket/16583

Solution 8 - Python

I needed to disable auto_now for a DateTime field during a migration and was able to do this.

events = Events.objects.all()
for event in events:
    for field in event._meta.fields:
        if field.name == 'created_date':
            field.auto_now = False
    event.save()

Solution 9 - Python

I'm late to the party, but similar to several of the other answers, this is a solution I used during a database migration. The difference from the other answers is that this disables all auto_now fields for the model under the assumption that there's really no reason to have more than one such field.

def disable_auto_now_fields(*models):
    """Turns off the auto_now and auto_now_add attributes on a Model's fields,
    so that an instance of the Model can be saved with a custom value.
    """
    for model in models:
        for field in model._meta.local_fields:
            if hasattr(field, 'auto_now'):
                field.auto_now = False
            if hasattr(field, 'auto_now_add'):
                field.auto_now_add = False

Then to use it, you can simply do:

disable_auto_now_fields(Document, Event, ...)

And it will go through and nuke all of your auto_now and auto_now_add fields for all of the model classes you pass in.

Solution 10 - Python

A bit more clean version of context manager from https://stackoverflow.com/a/35943149/1731460

from contextlib import contextmanager

@contextmanager
def suppress_auto_now(model, field_names):
    """
    idea taken here https://stackoverflow.com/a/35943149/1731460
    """
    fields_state = {}
    for field_name in field_names:
        field = model._meta.get_field(field_name)
        fields_state[field] = {'auto_now': field.auto_now, 'auto_now_add': field.auto_now_add}

    for field in fields_state:
        field.auto_now = False
        field.auto_now_add = False
    try:
        yield
    finally:
        for field, state in fields_state.items():
            field.auto_now = state['auto_now']
            field.auto_now_add = state['auto_now_add']

You can use it even with Factories (factory-boy)

        with suppress_autotime(Click, ['created']):
            ClickFactory.bulk_create(post=obj.post, link=obj.link, created__iter=created)

Solution 11 - Python

From django docs

DateField.auto_now_add

Automatically set the field to now when the object is first created. Useful for creation of timestamps. Note that the current date is always used; it’s not just a default value that you can override. So even if you set a value for this field when creating the object, it will be ignored. If you want to be able to modify this field, set the following instead of auto_now_add=True:

For DateField: default=date.today - from datetime.date.today()

For DateTimeField: default=timezone.now - from django.utils.timezone.now()

Solution 12 - Python

copy of https://stackoverflow.com/questions/39883335/django-models-datetimefield-changing-dynamically-auto-now-add-value/45946742#45946742

Well , I spent this afternoon find out and the first problem is how fetch model object and where in code . I'm in restframework in serializer.py , for example in __init__ of serializer it could not have the Model yet . Now in to_internal_value you can get the model class , after get the Field and after modify the field properties like in this example :

class ProblemSerializer(serializers.ModelSerializer):

    def to_internal_value(self, data): 
        ModelClass = self.Meta.model
        dfil = ModelClass._meta.get_field('date_update')
        dfil.auto_now = False
        dfil.editable = True

Solution 13 - Python

I needed solution that will work with update_or_create, I've came to this solution based on @andreaspelme code.

Only change is that You can set skipping by setting modified field to skip not only by actually passing kwarg skip_modified_update to save() method.

Just yourmodelobject.modified='skip' and update will be skipped!

from django.db import models
from django.utils import timezone


class TimeTrackableAbstractModel(models.Model):
    created = models.DateTimeField(default=timezone.now, db_index=True)
    modified = models.DateTimeField(default=timezone.now, db_index=True)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        skip_modified_update = kwargs.pop('skip_modified_update', False)
        if skip_modified_update or self.modified == 'skip':
            self.modified = models.F('modified')
        else:
            self.modified = timezone.now()
        super(TimeTrackableAbstractModel, self).save(*args, **kwargs)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjedieView Question on Stackoverflow
Solution 1 - PythondirleyrlsView Answer on Stackoverflow
Solution 2 - PythonandreaspelmeView Answer on Stackoverflow
Solution 3 - PythonApollo DataView Answer on Stackoverflow
Solution 4 - PythonFrank HenardView Answer on Stackoverflow
Solution 5 - PythonsoulseekahView Answer on Stackoverflow
Solution 6 - PythonHamish DownerView Answer on Stackoverflow
Solution 7 - PythonPavel VergeevView Answer on Stackoverflow
Solution 8 - Pythonuser1172023View Answer on Stackoverflow
Solution 9 - PythonmlissnerView Answer on Stackoverflow
Solution 10 - PythonpymenView Answer on Stackoverflow
Solution 11 - PythonahprosimView Answer on Stackoverflow
Solution 12 - PythonSérgioView Answer on Stackoverflow
Solution 13 - PythonlechupView Answer on Stackoverflow