0

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.

Ricardo Vilaça
  • 846
  • 1
  • 7
  • 18

3 Answers3

3

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.

  • Hi @Quintin, impressive answer!! How do you override the save method? Is this a good example: https://stackoverflow.com/questions/4269605/django-override-save-for-model ? I'm new to webdev so excuse my lack of information about such basic topics.. By the way, where do you get the "start_date" on the queryset filter that you showed? – Ricardo Vilaça Jun 19 '20 at 14:13
  • Apologies that was a typo. Should be start_time. Amended accordingly. The above topic for save is perfect. Just check if there is a stamped_date field with an if statement inside your save method and assign a value if empty, as to the save example. Always return super in your save method after your code – Quintin Walker Jun 19 '20 at 14:47
  • That wouldn't work because i need to filter on the "stamped_date", not on the "start_date" which is not an obligatory field. Anyway i already made it with the save() method! Thank you very much – Ricardo Vilaça Jun 19 '20 at 15:53
2

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.

Community
  • 1
  • 1
S.Moenig
  • 80
  • 7
1

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

spaceSentinel
  • 630
  • 6
  • 9
  • Thanks, it's a good answer but unfortunatelly not the one i need because i just found out that i can't filter on models' methods.. – Ricardo Vilaça Jun 19 '20 at 13:51
  • @Ricardo Why not convert the method field to a model field, you could add the calculation logic by overriding the save method. – spaceSentinel Jun 19 '20 at 14:23
  • Ashtana thanks for you help! I was already looking into it (i didn't know about overwriting the save method). I made it work :) thanks – Ricardo Vilaça Jun 19 '20 at 15:54