Adharsh M
Adharsh M

Reputation: 3832

Django getting related objects maintaining parent's order in a queryset and getting next object

I have the following structure in a django application

class Course(models.Model):
    name = models.CharField(_("Course Name"), max_length=50)

class Module(models.Model):
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    name = models.CharField(_("Module Name"), max_length=50)
    number = models.IntegerField(_("Module Number"))
    
class Chapter(models.Model):
    module = models.ForeignKey(Module, on_delete=models.CASCADE)
    name = models.CharField(_("Chapter Name"), max_length=50)
    number = models.IntegerField(_("Chapter Number"))

I wanted to fetch all Chapters that is contained in a course, with the order of the module (based on module number) and the order of chapter ( based on chapter number )

for example the hierarchy can be like this

Course 1 has

Module 1
-- Chapter number=1 (1.1)
-- Chapter number=2 (1.2)
-- Chapter number=3 (1.3)
Module 2
-- Chapter number=1 (2.1)
-- Chapter number=2 (2.2)
-- Chapter number=3 (2.3)

Course 2 has,

Module 1
-- Chapter 1
-- Chapter 2
-- Chapter 3
Module 2
-- Chapter 1
-- Chapter 2
-- Chapter 3

I would like to fetch All chapters in Course 1 such in the order

[ (1.1), (1.2), (1.3), (2.1), (2.2), (2.3) ] and so on...

Then I would like to implement next_chapter property in model which would give Chapter 2.1 object after 1.3.

class Chapter(models.Model):
    @property
    def next_chapter(self):
        ???
     

When i fetch all chapters with the query

Chapter.objects.filter(module__in=Course1.module_set.all())

I'm getting the chapters ordered by

[ (1.1), (2.1), (1.2), (2.2), (3.1), (3.2) ]...

As the chapter number is 1 2 3 etc...

Upvotes: 0

Views: 98

Answers (1)

AKX
AKX

Reputation: 169388

order_by clauses can also use __ to reach across relationships.

Similarly, you can simplify the course lookup to span the relationship to avoid the __in stuff:

qs = Chapter.objects.filter(module__course=Course1).order_by(
    "module__number", "number"
)

If you have a chapter and you want to get the next chapter, you'll want something like this – the idea is, that since the qs queryset from above is in correct order already, all we need to do is find

  • a chapter in the same module that has a number greater than the current one, or
  • any chapter in a subsequent module

and then take the first one (in that correct sorted order):

current_chapter = ...  # assume this is the current chapter

next_qs = qs.filter(  # qs is from above
  Q(module=current_chapter.module, number__gt=current_chapter.number) |  # same module, next chapter number
  Q(module__number__gt=current_chapter.module.number)  # next module
)

next_chapter = next_qs.first()  # first filtered result in same sort order

Upvotes: 1

Related Questions