How to delete a record after a certain time of it's creation in Django?

Question:

I am building an application that has a ‘Story Feature’ which is quite similar to Instagram’s story feature, So I want to delete a story after 24 hours of its creation. So if a story was created on 12:00 PM 1 January 2021, I want to delete it automatically at 12:00 PM 2 January, 2021. I am using django3.1

My Model:

class Story(models.Model):
    user = models.ForeignKey(to=User, on_delete=models.CASCADE)
    text = models.CharField(max_length=200)
    image = models.ImageField(blank=True, null=True)
    video = models.FielField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    expiration_time = models.DateTimeField()

I want to delete every record after their particular expiration time.

[Expiration time is set in a post_save function ]

Asked By: Khan Asfi Reza

||

Answers:

I have learned that it simple solutions are often better than more beautiful, complex ones.

If this were me, I would run a scheduled process that runs once an hour and checks each row in the sql table. Then, check if post time was greater than 24 hours ago.

If you really want to get fancy, I would recommend Redis to create a scheduled process upon creation of the post. Schedule a task in 24 hours that deletes that particular post.

Answered By: Thatcher Thornberry

The most robust way to do this is typically a combination of filtering, and removing the items with a task you run every hour/day/week.

We thus can retrieve the items that are not yet expired with:

from django.db.models.functions import Now

stories = Story.objects.filter(expiration_time__lt=Now())

If you need to filter often, it might be more interesting to make a manager [Django-doc] for this.

from django.db import models
from django.db.models.functions import Now

class StoryManager(models.Manager):

    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).filter(
            expiration_time__lt=Now()
        )


class Story(models.Model):
    # …
    xpiration_time = models.DateTimeField(db_index=True)
    
    objects = StoryManager()

The db_index=True part [Django-doc] will normally improve performance to filter expired objects.

Of course this does not remove the elements, it simply will not retrieve these (and thus if you filter these in the views, then you will not see these).

You can make a periodic task, for example a cronjob [wiki] that will run every day, week, month, hour to remove the elements. You can do this by defining a management command [Django-doc]:

# app_name/management/commands/remove_expired_stories.py

from django.core.management.base import BaseCommand
from django.db.models.functions import Now

from app_name.models import Story

class Command(BaseCommand):
    help = 'Remove expired stories'

    def handle(self, *args, **options):
        Story._base_manager.filter(expiration_time__gte=Now()).delete()

Then you can manually run:

python3 manage.py remove_expired_stories

or you can schedule this task to run periodically. This is more robust: if for some reason the scheduler does no longer work, then the filtering will still prevent that you can see the Storys that are expired. It is also simpler and more efficient than scheduling an expiration script per story, especially since if later the expiration time changes, it will be quite complicated to remove the scheduled items, and reschedule these.

Answered By: Willem Van Onsem

I think willem van onsem is right… i mean his method

       ###LOGIC 

time A i.e Now()---------somwhere in the future -------------------------timeB i.e expiration_time
if time A > time B its expired 
if time A < time B its not expired  

so with

  ...
 expiration_time = models.DateTimeField(db_index= True,default=Now()+timedelta(days=1), editable = False)

in your models and in your manager

class StoryManager(models.Manager):

def get_queryset(self, *args, **kwargs):
    return super().get_queryset(*args, **kwargs).filter(
        expiration_time__gt=Now()
    )

then in the command/.py this
class Command(BaseCommand):
help = "clear expired messages"

def handle(self, *args, **options):
    Story._base_manager.filter(expiration_time__lte=Now()).delete()
    

all should be well.

Answered By: f_iasco