35

as the title suggests. I want to add 30 days to the DateField field. This is auto populated on creation of record using auto_now_add=True

Any ideas how to go about doing this?

Thanks

dotty
  • 40,405
  • 66
  • 150
  • 195
  • 2
    This is a guess, but Django Forms allow function calls in their `initial=` values. If this is the case also for models, then you could remove auto_add_now and replace it with `default=lambda: datetime.now()+timedelta(days=30)` – Tomasz Zieliński Feb 04 '10 at 11:49

5 Answers5

68

There is no need to implement custom save method.

Also doing this default=datetime.now()+timedelta(days=30) is absolutely wrong! It gets evaluated when you start your instance of django. If you use apache it will probably work, because on some configurations apache revokes your django application on every request, but still you can find you self some day looking through out your code and trying to figure out why this get calculated not as you expect.

The right way of doing this is to pass a callable object to default argument. It can be a datetime.today function or your custom function. Then it gets evaluated every time you request a new default value.

def get_deadline():
    return datetime.today() + timedelta(days=20)

class Bill(models.Model):
    name = models.CharField(max_length=50)
    customer = models.ForeignKey(User, related_name='bills')
    date = models.DateField(default=datetime.today)
    deadline = models.DateField(default=get_deadline)
Simanas
  • 2,793
  • 22
  • 20
  • 3
    I use this alot! Especially useful when you want to get all posts that are no older than 20 days or so. Just use `BlogPost.objects.filter(pub_date__gt=datetime.now()-timedelta(days=20)´. This translates into SQL and is therefore very effective! [Reference in django docs](https://docs.djangoproject.com/en/dev/ref/models/querysets/#field-lookups) – Martin Hallén Oct 22 '14 at 21:40
  • @mart0903 your code was just what I needed: `BlogPost.objects.filter(pub_date__gt=datetime.now()-timedelta(days=20)` In other words, only objects with pub_date newer than 20 days ago. – Bob Stein Jan 06 '16 at 21:06
  • 1
    default=lambda: datetime.now()+timedelta(days=30) – Shervin Gharib Feb 07 '16 at 06:44
  • 1
    @exshinigami using lambdas in model field options is not supported anymore: https://github.com/django/django/commit/9673013abe4cd64183bf55d89adfa04927e3b947 – Paolo Jun 28 '16 at 13:34
  • 1
    This is the only answer I found that DOESN'T create a new migration every time you run `makemigrations`. Thanks for the working solution @Simanas. – Kalob Taulien Feb 23 '19 at 05:27
  • Using callable is totally right because it updates every time you save a new data. Not using a callable seems to save the data that the server goes up (the data always will be the same). Thanks! – igorkf Jul 04 '21 at 20:56
59

// Update

The comment under the original post got me thinking. I guess this is the best solution so far:

from datetime import datetime, timedelta

class MyModel(models.Model):
   mydate = models.DateTimeField(default=datetime.now()+timedelta(days=30))

// 2. Update

If you want to define a model attribute which holds the amount of days that should be added you are going to need to override the save method. So far I could'nt come up with a simpler way.

Solution:

class MyModel(models.Model):
  mydate = models.DateTimeField(editable=False)
  daysadded = models.IntegerField()

  def save(self):
    from datetime import datetime, timedelta
    d = timedelta(days=self.daysadded)

    if not self.id:
      self.mydate = datetime.now() + d
      super(MyModel, self).save()

As becomingGuru already suggested you should override your models save method.

Example:

class MyModel(models.Model):
  mydate = models.DateTimeField(auto_now_add=True)      

  def save(self):
    from datetime import timedelta
    d = timedelta(days=30)

    // only add 30 days if it's the first time the model is saved
    if not self.id:
      // not saving the model before adding the timedelta gave me errors 
      super(MyModel, self).save()

      self.mydate += d

      // final save
      super(MyModel, self).save()

This is not the best way for me since you have to save the model twice. But using auto_now_add requires you to save the model first before a datetime instance for mydate is created.

Another approach which would require only one save:

class MyModel(models.Model):
  mydate = models.DateTimeField(editable=False) // editable=False to hide in admin

  def save(self):
    from datetime import datetime, timedelta
    d = timedelta(days=30)

    // only add 30 days if it's the first time the model is saved
    if not self.id:
      self.mydate = datetime.now() + d
      super(MyModel, self).save()

Hope that helped!

Jens
  • 20,533
  • 11
  • 60
  • 86
  • UPDATE: Worked as expected, however the 30 days is going to be a variable. It could be 30 days, 60 days, 90 days, etc. Any ideas? Should i add a 'expires_in' field which holds the amount of days it expires in? How could i use this new 'expires_in' field to edit the line "timedelta(days=30)" ? – dotty Feb 04 '10 at 12:57
  • Do I understand you correctly? You want another model field which holds a value describing how much days are going to be added? – Jens Feb 04 '10 at 13:29
  • Overriding the save method is the "django" way to do it. – Andrew Sledge Feb 04 '10 at 13:38
  • 12
    I might be wrong but I think your first solution (with `default=datetime.now()+timedelta(days=30)`) is wrong. The module is loaded once at server startup, so `datetime.now()` is only executed when the server starts (not when a new instance is added). – Felix Kling Feb 04 '10 at 22:10
  • 1
    The first part of the answer is incorrect. You must pass a callable like @Simanas answer states or else you won't get what you need. – alfetopito Jan 11 '16 at 20:10
  • You should always call the super save method, whatever test you make, so you can unindent the last line of your example. Also, you could put the import datetime on top of your Python script and create the timedelta variable in your if statement. – Q Caron Sep 10 '16 at 13:26
  • 1
    @FelixKling is right, setting the default value of a `DateTimeField` to anything with `datetime.now()` in it is completely wrong; the function will be evaluated at the same time as the class definition, so not only will the default change at every server startup, but also whenever you run `makemigrations`; in effect you can run it as many times as you like, and every time a "change" to that field will be detected. – user4520 Jan 22 '19 at 10:19
5

Use Pythons timedelta:

from datetime import timedelta
d = timedelta(days=30)

# object is your current instance of the model
object.yourdatefield += d
# or this because I am not sure whether the previous works
object.yourdatefield = object.yourdatefield + d

object.save()

And from the Django documentation:

DateField
A date, represented in Python by a datetime.date instance.

If you want to add 30 days on creation of the object, forget about auto_now_add=True and do as becomingGuru suggests. Information about overriding save() can also be found in the Django documentation.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
1

Override the save on the model and while saving, check if pk is populated.

>>> from datetime import datetime
>>> from datetime import timedelta
>>> cur_date = datetime.now()
>>> cur_date
datetime.datetime(2010, 2, 4, 5, 0, 24, 437405)
>>> cur_date+timedelta(days=30)
datetime.datetime(2010, 3, 6, 5, 0, 24, 437405)
lprsd
  • 84,407
  • 47
  • 135
  • 168
0

doc for timedelta: https://docs.python.org/2/library/datetime.html

def make_timedelta(seconds):
        return timedelta(days=seconds // 86399, seconds=seconds % 86399)
Arseny
  • 111
  • 12