Hi, I’m new to Django
and Web development in general. This is my first time trying web development.
I am enjoying Django so far and I am trying to create what I have been looking for a while and could not find. A self hosted course video organizer, something like Jellyfin
but for course videos like a video from FreeCodeCamp
.
I first though of creating models in a way that represents a TV show in Jellyfin
. For example a directory will represent a series or a course, sub-directories (if present) will represent seasons of a series or chapters in a course and video files inside these sub-directories will represent episodes or topics in a chapter. And if there is only a single video file that will be taken as course as well with no directories.
I am looking for suggestions, tips and feedback on how should I design the models.
So far I came up with this,
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.core.validators import RegexValidator, FileExtensionValidator
from django.urls import reverse
from taggit.managers import TaggableManager
class Course(models.Model):
title = models.CharField(max_length=150)
author = models.ForeignKey(User, on_delete=models.CASCADE)
description = models.TextField()
tags = TaggableManager()
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('course-detail', kwargs={'pk': self.pk})
class Chapter(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='chapters')
title = models.CharField(max_length=255)
order = models.IntegerField(default=0)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('videos-detail', kwargs={'pk': self.pk})
class Videos(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='videos')
chapter = models.ForeignKey(Chapter, on_delete=models.CASCADE, related_name='videos', null=True, blank=True)
title = models.CharField(max_length=150)
video = models.FileField(upload_to='videos',null=True, validators=[
FileExtensionValidator(allowed_extensions=[
'MOV',
'avi',
'mp4',
'webm',
'mkv'
])
])
thumbnail = models.ImageField(upload_to='thumbnails')
description = models.TextField()
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('videos-detail', kwargs={'pk': self.pk})
And here is the views.py
for this app,
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth.models import User
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.conf import settings
import os
from PIL import Image
from moviepy.video.io.VideoFileClip import VideoFileClip
from .models import Course, Videos
class VideoListView(ListView):
model = Videos
context_object_name = 'videos'
ordering = ['-date_created']
paginate_by = 5
class UserVideoListView(ListView):
model = Videos
template_name = 'courses/user_videos.html' # <app>/<model>_<viewtype>.html
context_object_name = 'videos'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Videos.objects.filter(author=user).order_by('-date_created')
class VideoDetailView(DetailView):
model = Videos
class VideoCreateView(LoginRequiredMixin, CreateView):
model = Videos
fields = ['title', 'video', 'course']
def form_valid(self, form):
form.instance.author = self.request.user
# Get the current video object being saved
video_obj = form.save()
# Load the video clip
clip = VideoFileClip(os.path.join(settings.MEDIA_ROOT, str(video_obj.video)))
# Get the first frame of the video as an image
thumbnail = clip.get_frame(0)
# Convert the array to a PIL image
thumbnail_image = Image.fromarray(thumbnail)
# Save the thumbnail as an image file
thumbnail_filename = os.path.join(settings.MEDIA_ROOT, "thumbnails", f'{str(video_obj.id)}_thumb.jpeg')
thumbnail_image.save(thumbnail_filename, format='JPEG')
print(f"FILENAME IS: {thumbnail_filename}")
# Set the thumbnail_url field of the video object to the path of the thumbnail file
video_obj.thumbnail = os.path.join('thumbnails', f'{str(video_obj.id)}_thumb.jpeg')
video_obj.save()
return super().form_valid(form)
class VideoUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Videos
fields = ['title', 'video']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
video = self.get_object()
if self.request.user == video.author:
return True
return False
class VideoDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Videos
success_url = '/'
def test_func(self):
video = self.get_object()
if self.request.user == video.author:
return True
return False
class CourseListView(ListView):
model = Course
context_object_name = 'courses'
class CourseDetailView(DetailView):
model = Course
context_object_name = 'course'
class CourseCreateView(CreateView):
model = Course
fields = ['title', 'description', 'author', 'tags']
success_url = reverse_lazy('course-list')
class CourseUpdateView(LoginRequiredMixin, UpdateView):
model = Course
fields = ['title', 'description', 'author', 'tags']
context_object_name = 'course'
class CourseDeleteView(LoginRequiredMixin, DeleteView):
model = Course
success_url = reverse_lazy('course-list')
context_object_name = 'course'
I am not sure if this is the correct. I am looking for some suggestions and feedback.