Could anyone please explain use cases for relationships between models when using FKs?

To preface, I come from a SQL background.

I have these two models;

Class EventType(models.Model):
    event_type_id = models.AutoField(primary_key = True)
    event_type = models.CharField(max_length=10)
    event_priority = models.IntegerField()
    
    class Meta:
        managed = True
        db_table = 'app_event_type'
        
class Event(models.Model):
    event_id = models.AutoField(primary_key = True)
    event_name = models.CharField(max_length=25)
    event_type = models.ForeignKey(EventType, on_delete=models.CASCADE)
    is_active = models.BooleanField(default=False)
    
    class Meta:
        managed = True
        db_table = 'app_event'

I’ve been playing around in the Shell to see what I can do.

Here is one test. This is really cool because oyu can filter using criteria from each model together.

>>> from app_test.models import *
>>> ee = EventType.objects.get(event_type_id=1).event_set.all()
<QuerySet [<Event: Event object (2)>, <Event: Event object (3)>]>
>>>

But I am not understanding the practicality / purpose. I am not saying there isn’t one / any - I am saying I don’t see it and am hoping someone can shed some light.

I assume you’re supposed to add something like this to the end of your Model classes. That way it actually returns something useful rather than IDs.

def __str__(self):
    #    return {self.event_name}

But what would you use these for? I guess when I need to return the type of something this makes it easy for me, but I feel like that is super limited. Which makes me feel like I am missing something pretty major.

What are some common use cases for these relationships / reverse relationships? Is it simply a way to join data with no work on our part? Meaning, django just handles the JOINs for us and then we architect our app to utilize that the best way possible to meet our needs, or are their some intended use cases?

Thanks.

Effectively, yes.

First, what you get back from these querysets are instances of the corresponding model, not just the IDs. The default __str__ method prints the IDs, but in your code, you have the complete object available to you.

The statement ee = EventType.objects.get(event_type_id=1) means that ee contains a reference to an instance of EventType. You have direct access to each of the fields in that model as ee.event_type and ee.event_priority.

When you extend that statement to become ee = EventType.objects.get(event_type_id=1).event_set.all() you have effectively shortened these two statements to one:

et = EventType.objects.get(event_type_id=1)
ee = et.event_set.all()

In either case (1 statement or 2 since they are effectively the same), ee contains a reference to a queryset, but it is a queryset of Event, not EventType

As with any other queryset, you can iterate over those results, and access the individual fields of the Event model.
e.g.

for an_event in ee:
    print(an_event.name, an_event.type)

Side note 1: Personally, I am not a fan of this formulation.
(This being EventType.objects.get(event_type_id=1).event_set.all()) It’s not obvious to me that what is being selected are Event objects and not EventType objects. I would typically recommend:
Event.objects.filter(event_type_id=1)

Side note 2: you can see a representation of the SQL being generated for a queryset by printing the .query attribute of that queryset.

>>> ee = EventType.objects.get(event_type_id=1).event_set.all()
<QuerySet [<Event: Event object (2)>, <Event: Event object (3)>]>

If you then print(ee.query), you’ll see the Django-internal representation of the SQL. (Not the real SQL, but close enough for understanding.)

Final note, you wrote:

What is the “these” you are asking about? You’re touching on a number of different topics in your post and I’m unsure of the referent.

Ken! You are the man! I was looking at this wrong and it was right in front of my face. I definitely need to re read what you wrote and try some things now.

You also hit on something I kept thinking myself,

Side note 1: Personally, I am not a fan of this formulation.
(This being EventType.objects.get(event_type_id=1).event_set.all() ) It’s not obvious to me that what is being selected are Event objects and not EventType objects. I would typically recommend:
Event.objects.filter(event_type_id=1)

I actually wrote that to make a point - that some times it isn’t crystal clear what things to refer to using the ORM and that writing things a certain way make them more clear is important. It was also me “riffing” in the Shell to see what it could do, but I just kept adding onto it to see how bundled up I could make it.

After I re read and test, I’ll probably have another set of questions, but my main one right now is - So this returns an object. Which means I have all fields available to me. So I don’t necessarily have to do, Event.objects.filter(event_type_id=1). I could actually filter where EventType’s event_type = holiday as well, correct?

Thanks so much for the crystal clear explanation.

I think it would be more like Event.objects.filter(event_type__event_type='holiday'), but yea.

The first event_type in the filter is a reference to the field within Event.
The double-underscores are the specification to follow that FK to the target object, in this case EventType.
The second event_type then is the field reference within EventType.

(Syntactically, trying to specify something like Event.objects.filter(event_type.event_type='holiday') within Python would have a completely different meaning, and so Django uses the double-underscore notation to work around that.)

Thanks again. I’m definitely going to test around tonight.