Opened 11 years ago

Closed 9 years ago

Last modified 9 years ago

#22571 closed Cleanup/optimization (fixed)

Document implications of using auto_now_add=True and get_or_create

Reported by: nu.everest@… Owned by: Yamila
Component: Documentation Version: 1.4
Severity: Normal Keywords: integrityerror auto_now_add get_or_create duplicatekey
Cc: me@… Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Russell Keith-Magee)

Example:

# Given this simple model
class Foo(models.Model):
    name = models.CharField(max_length=100)
    date_added = models.DateTimeField(auto_now_add=True)

# This will always be true, even if an instance
# with this name and today's date already exists

bar, created = Foo.objects.get_or_create(
    name = 'Alex', 
    date_added = some_datetime_obj
)

print created
# >> True


# The problem is, auto_now_add does some stuff that 
# makes it uneditable, and messes up my expectations
# when using it with get_or_create

# Here's the solution
class Foo(models.Model):
    name = models.CharField(max_length=100)
    date_added = models.DateTimeField(default=datetime.today())

bar, created = Foo.objects.get_or_create(
    name = 'Alex', 
    date_added = some_datetime_obj
)
 
print created
# >> False

Error:
django.db.utils.IntegrityError: duplicate key value violates unique constraint

These two links document the issue:
http://alexkehayias.tumblr.com/post/33889961953/django-gotcha-duplicate-models-when-using

http://stackoverflow.com/questions/9297422/get-or-create-failure-with-django-and-postgres-duplicate-key-value-violates-uni

Change History (10)

comment:1 by Tim Graham, 11 years ago

Component: UncategorizedDocumentation
Summary: DateTimeField(auto_now_add=True) BreaksDocument implications of using auto_now_add=True and get_or_create
Triage Stage: UnreviewedAccepted
Type: UncategorizedCleanup/optimization

I suppose the documentation could have a note about this.

comment:2 by Markus Holtermann, 11 years ago

Your solution is not a solution at all, because your default on the DateTimeField is evaluated only once at model loading. You can use Django's django.utils.timezone.now (w/o the trailing ()).

comment:3 by Russell Keith-Magee, 11 years ago

Description: modified (diff)

comment:4 by mardini, 11 years ago

Note that your date_added is a DateTimeField, not a DateField, so it takes time into account. That's why using "today's date" won't match the existing value of date_added (since it's today's date + a particular time). If however some_datetime_obj in your first is exactly the same date and time, no new object will be created. If you don't want to consider the time in your get_or_create(), you can either use a DateField for date_added, or filter against year/month/day of your DateTimeField. So I think this ticket is invalid.

in reply to:  4 ; comment:5 by Simon Charette, 10 years ago

Replying to mardini:

Note that your date_added is a DateTimeField, not a DateField, so it takes time into account. That's why using "today's date" won't match the existing value of date_added (since it's today's date + a particular time). If however some_datetime_obj in your first is exactly the same date and time, no new object will be created. If you don't want to consider the time in your get_or_create(), you can either use a DateField for date_added, or filter against year/month/day of your DateTimeField. So I think this ticket is invalid.

I agree that the second example is a bit wrong but there's a legitimate issue to be documented here.

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

class Auto(models.Model):
    added = models.DateTimeField(auto_now_add=True)

class Default(models.Model):
    added = models.DateTimeField(default=timezone.now)
In [1]: from app.models import Auto, Default, timezone

In [2]: added = timezone.now()

In [3]: Auto.objects.get_or_create(added=added)
Out[3]: (<Auto: Auto object>, True)

In [4]: Auto.objects.get_or_create(added=added)
Out[4]: (<Auto: Auto object>, True)

In [5]: Default.objects.get_or_create(added=added)
Out[5]: (<Default: Default object>, True)

In [6]: Default.objects.get_or_create(added=added)
Out[6]: (<Default: Default object>, False)

in reply to:  5 comment:6 by Bibhas C Debnath, 10 years ago

Cc: me@… added

Replying to charettes:

In [1]: from app.models import Auto, Default, timezone

In [2]: added = timezone.now()

In [3]: Auto.objects.get_or_create(added=added)
Out[3]: (<Auto: Auto object>, True)

In [4]: Auto.objects.get_or_create(added=added)
Out[4]: (<Auto: Auto object>, True)

In [5]: Default.objects.get_or_create(added=added)
Out[5]: (<Default: Default object>, True)

In [6]: Default.objects.get_or_create(added=added)
Out[6]: (<Default: Default object>, False)

Isn't this expected? The doc says -

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.

And the code also shows that the value with always set to timezone.now() if auto_now_add is set, even if you provide a value when creating it.

Version 0, edited 10 years ago by Bibhas C Debnath (next)

comment:7 by Yamila, 9 years ago

Owner: changed from nobody to Yamila
Status: newassigned

comment:9 by Tim Graham <timograham@…>, 9 years ago

Resolution: fixed
Status: assignedclosed

In cbe4efc:

Fixed #22571 -- Added clarification about auto_now_add=True

comment:10 by Tim Graham <timograham@…>, 9 years ago

In 7dcfbb2e:

[1.8.x] Fixed #22571 -- Added clarification about auto_now_add=True

Backport of cbe4efcbc13ad402bf1f1a94b02a8ec93f20327d from master

Note: See TracTickets for help on using tickets.
Back to Top