Ricardo Vilaça
Ricardo Vilaça

Reputation: 1006

Filter model object by difference in days

My model has a date field and i want to filter the model by the last 7 days.

class Count(models.Model):
    task = models.ForeignKey(Task, related_name = 'counts', on_delete = models.CASCADE)
    start_time = models.DateTimeField(null = True, blank = True)
    end_time = models.DateTimeField(null = True, blank = True)
    time_spent = models.PositiveIntegerField()
    deleted = models.BooleanField(default = False)

    class Meta():
        ordering = ['accesses']

    def __str__(self):
        return f'{self.task.department} - {self.accesses.first().user} [{self.time_spent} min]'

    def stamped_date(self):
        if not self.start_time:
            return self.accesses.first().date
        return self.start_time

    def users(self):
        return list(set(map(lambda x: x.user, list(self.accesses.all()))))

I need to filter every count that has "stamped_date" in the last 7 days.

What i tried to do (in the model):

    def daysBy(self):
        return (datetime.now() - self.stamped_date()).days

to filter like this:

Count.objects.filter(daysBy__let = 7)

However, datetime.now() requires a timezone object, otherwise will throw the follwing error:

TypeError: can't subtract offset-naive and offset-aware datetimes

I'm also feeling this might not be the most correct way of achieving my goal, please correct me with a better way.

Or.. give me a way of inserting a timezone object related to the TIME_ZONE setting.

Upvotes: 0

Views: 68

Answers (3)

Quintin Walker
Quintin Walker

Reputation: 94

You want to filter the queryset and get a count if stamped_date is within seven days

You'll do yourself justice if this is a standard requirement to build a model manager to handle this

What you're aiming for. Because by doing this you can call count() on your returned queryset.

import datetime

    Count.objects.get_stamped_date().count()

Model Manager would be like so,

    class CountManager(models.Manager):

        def get_stamped_date(self):
            todays_date = timezone.now()
            seven_days_ago = datetime.timedelta(days=7)
            qs = super().get_queryset()
            count = qs.filter(start_time__range=[seven_days_ago, todays_date])

        return count

Then update your model to include the manager

Count(models.Models):
    objects = CountManager()

To be honest, your stamped_date should be a field attribute which should work with your save() method of your model so its a little confusing why you did it the way you did. Under the save method (standard) let the model instance check every time there isn't a start time and save your self.access datetime field which I dont see reference to here to the stamped_date field

Also just for a cherry on the top, would perhaps be even better to allow an argument to the model manager to say how many days so its not fixed to seven days.

Upvotes: 3

spaceSentinel
spaceSentinel

Reputation: 670

You could achieve this query using the range operator like this.

oldDate = datetime.now() - datetime.timedelta(days =7)
Count.objects.filter(start_time__range=(oldDate, datetime.now()))

EDIT : to achieve this kind of querying with the filter method, your method generated value should be a model field. You can achieve this by overriding the save method like this :

# Create the stamped date model field
stamped_date = models.DateTimeField(null=True)

#override the save method for your custom saving logic
def save(self, *args, **kwargs):
        if not self.start_time:
            self.stamped_date = self.accesses.first().date
        else:
            self.stamped_date = self.start_time
        super().save(*args, **kwargs)  # Call the "real" save() method.

Django docs

Upvotes: 1

S.Moenig
S.Moenig

Reputation: 80

from Time Zones - Django Documentation:

When time zone support is enabled (USE_TZ=True), Django uses time-zone-aware datetime objects. If your code creates datetime objects, they should be aware too. In this mode, the example above becomes:

   from django.utils import timezone

   now = timezone.now()

I hope this helps.

Upvotes: 2

Related Questions