Opened 12 months ago

Last modified 4 months ago

#22571 new Cleanup/optimization

Document implications of using auto_now_add=True and get_or_create

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

Description (last modified by russellm)

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 (6)

comment:1 Changed 12 months ago by timo

  • Component changed from Uncategorized to Documentation
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Summary changed from DateTimeField(auto_now_add=True) Breaks to Document implications of using auto_now_add=True and get_or_create
  • Triage Stage changed from Unreviewed to Accepted
  • Type changed from Uncategorized to Cleanup/optimization

I suppose the documentation could have a note about this.

comment:2 Changed 12 months ago by MarkusH

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 Changed 12 months ago by russellm

  • Description modified (diff)

comment:4 follow-up: Changed 11 months ago by 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.

comment:5 in reply to: ↑ 4 ; follow-up: Changed 11 months ago by charettes

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)

comment:6 in reply to: ↑ 5 Changed 4 months ago by iambibhas

  • 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.

Maybe we could update the doc to clarify what happens -

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 to this field when creating the object, it will be ignored. If you want to be able to modify this field, set default=timezone.now instead of this auto_now_add=True.

Last edited 4 months ago by iambibhas (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top