Home > Software engineering >  Django get next object given the current object in a queryset
Django get next object given the current object in a queryset

Time:06-18

I have a chapter model so in the chapter detail view i want the user to be able to navigate to the next and to previous chapter given the current chapter that he's viewing so what's is the most efficient way to that in django

this is my chapter model

class Chapter(models.Model):

    title         = models.CharField(max_length=100)
    slug          = models.SlugField(blank=True,null=True,)
    module       = models.ForeignKey(Module,on_delete=models.CASCADE,null=True,blank=True,)
    content       = models.TextField()
    
    def __str__(self):
         return self.title
    

   

i have seen some answers while they use the python list method to turn the queryset in to an array but i think maybe there is a better way 

CodePudding user response:

I thought about pagination but I wrote the fastest way.

class Chapter(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField(blank=True,null=True,)
    module = models.ForeignKey(Module,on_delete=models.CASCADE,null=True,blank=True,)
    content = models.TextField()
    
    def __str__(self):
        return self.title

    def get_next(self):
        return Chapter.objects.filter(id__gt=self.id).order_by('id').first()
        

in HTML

You need to call get_next in chapter objects loop. I suppose your chapter URL as detail/<id>

  <a href= {% url 'detail' obj.get_next.id }}> next chapter </a>
    

CodePudding user response:

You can provide them in a context of detail view. For example like previous_chapter, current_chapter and next_chapter as follow

class ChapterDetailView(DetailView):
    model = Chapter
    template_name = 'chapter_detail.html'
    context_object_name = 'current_chapter'
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['previous_chapter'] = your_previous_chapter_link_if_any_else_none
        context['next_chapter'] = your_next_chapter_link_if_any_else_none
        return context

Another approach may be defining urls in models.py as follow

class Chapter(models.Model):
    # ... your models field
    def get_next_chapter_url(self):
        next_chapter = Chapter.objects.filter(id__gt=self.id).order_by('id').first()
        if next_chapter:
            return reverse('chapter-detail', kwargs={'id': next_chapter.id})

And in chapter detail page template assuming context object name as current_chapter

<a href="{{current_chapter.get_next_chapter_url}}">Next Chapter</a>

CodePudding user response:

I'd be careful about using id as a positional identifier, as that is only dependable if chapters are created consecutively - you can't create one module of chapters, then another, then add a final chapter on the first module, and you can't rearrange chapters later. I'd extend the model to include a order field, unique for module

in your model

ordering = models.PositiveSmallIntegerField()

meta:
    unique_together = (module, ordering) #so each module can only have one chapter with ordering of 1

Then you could define a get_next_chapter method (get previous chapter would work similarly)

class Chapter(models.Model):
... fields
    def get_next_chapter_url(self):
        next_chapter_index = self.ordering   1
        next_chapter = Chapter.objects.filter(module = self.module, ordering = next_chapter_index).first()

But...while this will work, each reference to it is another database call. To be more efficient, you could utilise the ordering number in your view to get all the relevant chapters in a single query, eg:

views.py

def view_chapter(request, module_id, chap_num):
    #define vars
    chapters = [chap_num - 1, chap_num, chap_num   1]
    prev_chapter = None
    chapter = None
    next_chapter = None

    #perform single query
    queryset = Chapters.objects.filter(ordering__in=chapters, module_id=module_id).select_related('module') 
    #I've thrown selected_related in so you can use things like chapter.module.name in your template without additional database calls.
    
    #define context vars
    for chapter in queryset:
       if chapter.ordering == chap_num:
           chapter=chapter
       elif chapter.ordering == chap_num - 1:
           prev_chapter = chapter
       elif chapter.ordering == chap_num   1:
           next_chapter = chapter

    context = {"chapter": chapter, "prev-chapter": prev_chapter, "next_chapter": next_chapter}
        

and then in your template

<a href="{% url 'view chapter' chapter.module_id prev_chapter.ordering %}"> previous chapter</a>
  • Related