Django previous and next object buttons

Hello! I am trying to create a course web app.

We have courses > modules > videos

On the view course page, i have set the sidebar like this: (in order to have a dropdown with video links that belong to a module)

{% for module in modules %}

    {% if module.module_videos.count == 0 %}
    
    {% else %}
    Module name is: {{module.name}} <br>    
    Number of lessons in this module:
    {{module.module_videos.count}} <br>

        {% for video in module.module_videos.all %}
        <a href="{{video.get_absolute_url}}">{{video.title}}</a><br>
        {% endfor %}

    {% endif %}
{% endfor %}

Now when single video (lesson) is opened, i would like to have the Next button and Previous show somewhere on the page.

Can you please help me do that?

Here is the model file

from django.db import models
from django.urls import reverse


class Course(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True, blank=True, null=True)
    image = models.ImageField(upload_to='course/thumbnails')

    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return reverse('courses:course', args=[str(self.slug)])
    
    def clean(self):
        self.name = self.name.capitalize()
        self.slug = self.slug.lower()


class Module(models.Model):
    name = models.CharField(max_length=100, default='Module name', unique=True)
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='modules')

    def __str__(self):
        return self.name
    
    def clean(self):
        self.name = self.name.capitalize()

class Video(models.Model):
    title = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True)
    description = models.TextField(blank=True, null=True)

    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='videos')

    module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='module_videos')

    vimeo_id = models.CharField(max_length=50)

    # for next video
    order = models.IntegerField(default=1)

    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('courses:video', args=[str(self.course.slug), str(self.slug)])
    
    def clean(self):
        self.title = self.title.capitalize()
        self.slug = self.slug.lower()
    
    class Meta:
        ordering = ['order']

Here are the views

from django.shortcuts import render
from .models import Course, Module, Video
from django.contrib.auth.decorators import login_required


def courses_list(request):
    courses = Course.objects.all()
    context = {'courses': courses}
    return render(request, 'courses/courses-list.html', context)


# stranica kursa
def course(request, course_slug):
    course = Course.objects.get(slug=course_slug)

    modules = Module.objects.filter(course=course)

    context = {
        'course': course, 
        'modules': modules,
        }
    return render(request, 'courses/view-course.html', context)

@login_required
def video(request, course_slug, video_slug):
    video = Video.objects.get(slug=video_slug)
    next_video = Video.objects.filter(order__gt=video).order_by('order').first()

    context = {
        'video': video,
        'next_video': next_video
        }
    return render(request, 'courses/video.html', context)

As you can see, i tried something with next_video but i get the error immediately:

TypeError at /courses/course1/course-name/

Field ‘order’ expected a number but got <Video: Introduction Lesson>.

Hope somebody can give me an advice, thank you!

You want to compare the order field of video and not the video object. The filter would be order__gt=video.order.

1 Like

Thank you very much, it is fixed now.

Can you please share how to actually link to the next lesson?

I have tried this but i am getting 404

<a href="{{next_video}}">Go to next lesson</a>

Found it :slight_smile:



<a href="{{previous_video.get_absolute_url}}">Previous</a>

<a href="{{next_video.get_absolute_url}}">Next</a>

But is there a better way of structuring the lessons in modules? What if the user wants to change one lesson position, for example he created the lesson, but now wants to move that lesson in another module. Now that lesson will not link to previous/next like it should, because of the order number.

I need to continue this thread.

When creating the order field, the problem occures when adding a new course with it’s own modules and lessons.

Because when that new course has modules and lessons, each lesson must have an order number, but i can’t put 1, 2, 3 because previously created course lessons are using them.

Hope i can get some advice.

Thanks!

I don’t see a Lesson model in the OP, but I‘ll try my best here to guess intent. If one course can be associated to many lessons, and one lesson can be associated with many courses, then you need a Many to Many relationship. Since you need ordering, you need a through table for the relationship with an ordering field. The sortedm2m library might be helpful, or you can implement the ordering yourself.

Hi, sorry, i will post the models.py again in the next message, thank you :slight_smile:

Models.py

from django.db import models
from django.urls import reverse
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

from django.contrib.auth import get_user_model

User = get_user_model()

class Pricing(models.Model):
    name = models.CharField(max_length=100) # basic/pro/premium

    def __str__(self):
        return self.name

class Subscription(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)
    stripe_subscription_id = models.CharField(max_length=100)
    status = models.CharField(max_length=100)

    def __str__(self):
        return self.user.email


class Course(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True, blank=True, null=True)
    image = models.ImageField(upload_to='course/thumbnails')

    pricing_tiers = models.ManyToManyField(Pricing, blank=True)

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    #student = models.ManyToManyField()

    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return reverse('courses:course', args=[str(self.slug)])
    
    def clean(self):
        self.name = self.name.capitalize()
        self.slug = self.slug.lower()


class Module(models.Model):
    name = models.CharField(max_length=100, default='Module name', unique=True)
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='modules')

    def __str__(self):
        return self.name
    
    def clean(self):
        self.name = self.name.capitalize()

class Video(models.Model):
    title = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True)
    description = models.TextField(blank=True, null=True)

    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='videos')

    module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='module_videos')

    completed = models.BooleanField(default=False)

    vimeo_id = models.CharField(max_length=50)

    # for next video
    order = models.IntegerField(default=1, unique=True)

    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('courses:video', args=[str(self.course.slug), str(self.slug)])
    
    def clean(self):
        self.title = self.title.capitalize()
        self.slug = self.slug.lower()
    
    class Meta:
        ordering = ['order']



'''def post_save_user(sender, instance, created, *args, **kwargs):
    if created:
        free_trial_pricing = Pricing.objects.get(name="Free Trial")
        Subscription.objects.create(user=instance, pricing=free_trial_pricing)

post_save.connect(post_save_user, sender=User)'''

Unfortunately what you posted isn’t really helpful because I still don’t understand what a “Lesson” is in this context. I lack the specific domain knowledge in the relationships between “Course”, “Module”, and “Video” to help give you the exact answer. Nevertheless, you probably need something like this (this is just pseudo code):

class Course(models.Model):
    name = models.TextField()
    videos = models.ManyToManyField(through=Lesson)

class Video(models.Model):
   name = models.TextField()
   courses = models.ManyToManyField(through=Lesson)

class Lesson(models.Model):
  course = models.ForeignKey(Course)
  video = models.ForeginKey(Lesson)
  order = models.IntegerField()

class LessonResult(models.Model):
  lesson = models.ForeignKey(Lesson)
  user = models.ForeignKey(User)
  is_completed = models.BooleanField()
  • Many courses can re-use the same video as a “Lesson”.
  • Each course can put that video at a specific order
  • Many users can complete a single lesson.

Hope this helps

Thank you very much! I will try to be more clear and send a new message in this thread.

I think we are close, i just need some guidance, so i hope my next message will be better than my previous :slight_smile:

I will try to better explain the context.

Site owner / client, can log in. Site owner IS the teacher/client. I have a new question about adding employees, but that is for another time.

He can create courses.

Courses can have modules, like module 1, module 2, module 3…

Each module will have lessons.

So you can have a course for example:

“How to create a Django website”

Module 1

  • lesson 1
  • lesson 2

Module 2

  • lesson 3
  • lesson 4

Module 3

  • lesson 5
  • lesson 6
  • lesson 7

So we have a course, with 3 modules and 7 lessons in total (example).

I named the Lesson module “Video” by mistake, sorry. So the Lesson model can have different content, video, text, images, pdf…

It all works, i mean i did all crud operation for courses, modules, and videos (this will be chaged to “lessons” so i will refer to this model as lessons)

I also followed the advice i got about the order field, and i linked the lessons with that order field, which you can see above, where i have mentioned {{next_video.get_absolute_url}} and {{previous_video.get_absolute_url}}

And is working but: when i created a new course i saw that first problem. Order field is causing troubles because now the lessons can not really be linked with those previous and next buttons, not even if the order field is unique=False. I think there is no point in using that order field because it works only if there is just one course, where you manually add an order number for each lesson. So you would enter number 1 for lesson 1, number 2 for lesson 2 and so on. Then the system knows how to link them, and it does work, but only if there is just one course.

So that first issue is how to link them properly and set up models in a correct way, maybe this explains a bit better.

Sorry for a long message, i am just trying to find a solution, but i have reached a cap in learning because nowhere can i find the answers to these questions, all courses, videos, books are basically the same and do very similar things…

Thank you very much for your time, i am really grateful for any help.

Order field is causing troubles because now the lessons can not really be linked with those previous and next buttons, not even if the order field is unique=False. I think there is no point in using that order field because it works only if there is just one course, where you manually add an order number for each lesson. So you would enter number 1 for lesson 1, number 2 for lesson 2 and so on. Then the system knows how to link them, and it does work, but only if there is just one course.

In the original data model, with the original query, I think you were missing a filter by course (along with the assumption that you’re managing the order field correctly)

next_video = Video.objects.filter(
    order__gt=video.order, 
    course__slug=course_slug
).order_by('order').first()

If you wanted a data integrity check, you’re right that a unique constraint wouldn’t make sense, because you would probably want order=1 to be valid for each course (or module?). In that case, your unique constraint is over two fields, the course and the order.

From your explanation, my suggestions for many to many relationships may be unnecessary. However, if you plan to share lessons across courses/modules, then a many to many relationship is the way to go.

Thank you very much :slight_smile: