Querying Django model db - from Jose Portilla's Udemy course

I’m taking a Udemy course by Jose Portilla and I am learning how to query data in my Django shell.

To make things simple and straightforward, the instructor declared an age attribute for each person in the Patient model stored in the database.

But I am exploring a more accurate alternative. I’ve declared an alternate class attribute for the person’s date of birth (dob) and then I’ve added a class method called calculated_age() which returns a basic arithmetic operation which subtracts the person’s date of birth from the current date and time (now).

Here is my models.py:

from django.db import models
from datetime import datetime
from django.utils.timezone import now
from django.core.validators import MaxValueValidator, MinValueValidator


class Patient(models.Model):
   first_name = models.CharField(max_length=50)
   last_name = models.CharField(max_length=50)
   age = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(120)])
   heartrate = models.IntegerField(default=60,validators=[MinValueValidator(50), MaxValueValidator(180)])
   dob = models.DateTimeField(default=datetime(1957,6,6)) # Proper more precise date of birth to be calculated in method below
  
   def calculated_age(self):
       return datetime.date(now) - self.dob

   def __str__(self):
       return f"{self.first_name} {self.last_name} is {self.age} years old"

   def __repr__(self):
       return f"{self.first_name} {self.last_name} is {self.age} years old"

Does that look right? Am I missing anything above?

Next I am trying to get my Django shell to print the calculated_age() for any one of the entries in my database. Here is my shell input and output:

In [1]: from office.models import Patient

In [2]: obj = Patient

In [3]: Patient
Out[3]: office.models.Patient

In [4]: obj = Patient.objects.filter(first_name='Susan')

In [5]: obj
Out[5]: <QuerySet [Susan Smith is 33 years old]>

So that works so far. But how do I properly calculate the age of an instance of the Patient class in my Django shell?

Here is my best effort trying to calculate the precise age but unsuccessfully:


In [6]: obj.calculated_age
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 obj.calculated_age

AttributeError: 'QuerySet' object has no attribute 'calculated_age'

In [7]: obj.calculated_age()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 obj.calculated_age()

AttributeError: 'QuerySet' object has no attribute 'calculated_age'
In [8]: obj = Patient.objects.filter(first_name='Susan').calculated_age
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 obj = Patient.objects.filter(first_name='Susan').calculated_age

AttributeError: 'QuerySet' object has no attribute 'calculated_age'

In [9]: obj = Patient.objects.filter(first_name='Susan').calculated_age()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 obj = Patient.objects.filter(first_name='Susan').calculated_age()

AttributeError: 'QuerySet' object has no attribute 'calculated_age'

In [10]: 

Hey there!
It seems that you’re trying to call the calculate_age method in the queryset, but that’s a method of the Patient model.
For that, you will need to first retrieve a object from the queryset.

You can do that by calling first after the filter. That will return either the object, or None if your filter does not match any rows in the database.

Or you use get to retrieve a specific instance, or you iterate over the queryset to perform the calculation on every entry within the queryset.

Thank you both for your replies and insight so far.

Based on your feedback, I’m making progress but I am still not quite where I am aiming to be.

I modified my models.py method which now reads:

def calculated_age(self):
    td = timezone.now() - self.dob
    return str(td)

Here is my latest attempt at calling the above age calculation method showing input and output in my Django shell:

In [1]: from office.models import Patient
In [2]: obj = Patient.objects.get(first_name='Susan')
In [3]: obj.calculated_age()
Out[3]: '3:05:38.235830'
In [4]:

I’m leveraging the .get QuerySet as recommended by @KenWhitesell. As you can see in the output, a time delta is being printed. So that is progress. But there are still some outstanding issues:

The dob attribute has a default value of June 6th, 1957 as defined in the Model. However the time delta output shows 3 hours: 5 minutes: 38 seconds: and sundry milliseconds. The time difference is about 3 hours. But the output I am expecting is 65 years, 1 month, and a few days. What am I doing wrong here? How do I get Django to show the correct 65 year time delta?

I tried modifying the return value of the method to: return humanize.naturaldate(str(td)) (after installing humanize with pip and importing humanize at the top of the models.py), but that isn’t parsing as expected. Although I realize that even if it were parsing in a human-friendly readable format, this would just modify the formatting of the time which would not address the incorrect time delta (that’s showing 3 hours when it should show 65 years).

I also tried this return statement: return td.strftime("%Y %m %d") but that triggered this AttributeError: 'datetime.timedelta' object has no attribute 'strftime'.

[quote=“leandrodesouzadev, post:2, topic:15157”]It seems that you’re trying to call the calculate_age method in the queryset, but that’s a method of the Patient model.

For that, you will need to first retrieve an object from the queryset.

You can do that by calling first after the filter.[/quote]

You recommend retrieving an object from the QuerySet by calling the first_name after filter. I tried this already and in 4 different ways. Did you not read my original post entirely? To quote my original post at the top of this thread, here is a capture of my input into my Django shell:

from office.models import Patient
obj = Patient.objects.filter(first_name='Susan')

There I already successfully queried my db models which you recommended using the filter QuerySet. I continued with the following shell input:

obj.calculated_age()
obj = Patient.objects.filter(first_name='Susan').calculated_age()

All of these threw QuerySet AttributeErrors. You are recommending that I retrieve an object from the QuerySet (which I thought I already did with filter() using the instance first_name) and then invoke calculated_age() somehow. How are my failed attempts above different from what you are recommending, @leandrodesouzadev? What exactly are you suggesting I could try instead?

Please do me a favor and do the following in the shell.

obj = Patient.objects.get(first_name='Susan')
print(obj)
print(obj.dob)
print(type(obj.dob))
right_now = timezone.now()
print(right_now)
print(right_now - obj.dob)

Do not add any other formatting in this.

Also, regarding the remainder of your questions, you may wish to review the docs at QuerySet API reference | Django documentation | Django to see what data types are returned by each function.

1 Like

Here it is:

$ python manage.py shell
Python 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from office.models import Patient
In [2]: obj = Patient.objects.get(first_name='Susan')
In [3]: print(obj)
Susan Smith is 33 years old
In [4]: print(obj.dob)
2022-08-01 13:20:14.053409+00:00
In [5]: print(type(obj.dob))
<class 'datetime.datetime'>
In [6]: from django.utils import timezone
In [7]: right_now = timezone.now()
In [8]: print(right_now)
2022-08-01 18:02:32.922501+00:00
In [9]: print(right_now-obj.dob)
4:42:18.869092
In [10]: 

Now that I see your troubleshooting pattern, @KenWhitesell, I know what the problem is.

When I first added the dob line as a DateTimeField this morning, I passed in datetime.now as the default argument. So after migrating my db, the immediate date and time is what was entered, which was about 5 hours ago, which explains why when printing obj.dob in my shell it shows ~13 hr : 20 min (in UTC).

There are a few ways to fix this. I could create a new entry in the db which would have the default date of 1957 assigned at runtime. Alternatively, as you can see below, I just re-assigned the dob data in the db in an existing table:

$ python manage.py shell
Python 3.10.5 (main, Jun 6 2022, 18:49:26) [GCC 12.1.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from office.models import Patient
In [2]: from django.utils import timezone
In [3]: obj = Patient.objects.get(first_name='Carl')
In [4]: obj
Out[4]: Carl Smith is 25 years old
In [5]: obj.dob
Out[5]: datetime.datetime(2022, 8, 1, 13, 20, 14, 53409, tzinfo=datetime.timezone.utc)
In [6]: import datetime
In [8]: obj.dob = datetime.datetime(1972, 8,1)
In [9]: obj.dob
Out[9]: datetime.datetime(1972, 8, 1, 0, 0)
In [10]: obj.save()
/home/<user>/dev/projects/python/2018-and-2020/Jose_Portilla_Pierian_Data_Udemy_course/my_site/venv/lib/python3.10/site-packages/django/db/models/fields/__init__.py:1534: RuntimeWarning: DateTimeField Patient.dob received a naive datetime (1972-08-01 00:00:00) while time zone support is active.
warnings.warn(
In [11]: obj.dob
Out[11]: datetime.datetime(1972, 8, 1, 0, 0)
In [15]: right_now = datetime.datetime.now()
In [16]: print(right_now - obj.dob)
18262 days, 18:58:41.752385

Eureka! Hooray! At input entry [16] the correct time delta is showing. Although the formatting is off, at least it’s working now.

It’s also worth highlighting that in my experimentation above there were some inputs where I was encountering a TypeError: can't subtract offset-naive and offset-aware datetimes so I just resolved to declaring the right_now variable with a proper reference to the datetime module. I redacted those erroneous input/output from the code snippet above to clean it up a little.

The next steps for me to take include:

  • re-formatting the date time output into something more human readable. I’ve already explored the third party humanize module which I will still try to get working
  • getting the original calculated_age() class method to execute when I call it in my shell

I’ll continue to experiment with this. If I encounter any more issues as I take the next steps above, I will be sure to report back here.

Thank you @KenWhitesell for your advice and patience so far.

I will peruse the Django doc link you shared too. The Udemy course titled Django and Python Full Stack Developer Masterclass by Jose Portilla that I am taking devotes two full hours to QuerySets and most of the instructions are based on Django shell interaction. So the instructor covers the content of that Django doc you shared at length and beyond. My problem is that my Django study sessions are separated by weeks at a time so I seemingly have to re-learn half the material every time I sit down for my next session. If I could only work on Django a little bit everyday then my progress learning Django would be enhanced drastically. I wish I were more disciplined. Ugh. #FWP I guess. :slight_smile:

1 Like