Data 10
Data 10
when i login and check back the course detail page. i still see enroll button there
but i want to see the message instead that you are enrolled. i think it does not
add the user enrollement in database. Here is my backend and frontend code. Please
revise the complete code and update it so that enrollment will work smoothly. I am
using cookies which are autmatically generated when i login and deleted when i
logout.
backend
myapp/models.py
class Course(models.Model):
"""Model representing a course."""
title = models.CharField(max_length=200)
description = models.TextField()
video = models.FileField(upload_to='course_videos/', null=True, blank=True) #
Field for course video
technologies = models.TextField(blank=True) # Field for technologies used
teacher = models.ForeignKey(
User,
on_delete=models.CASCADE, # Delete courses when teacher is deleted
null=True,
limit_choices_to={'user_type': 'teacher'},
related_name='courses'
)
category = models.ForeignKey(
CourseCategory,
on_delete=models.SET_NULL, # Set to null if category is deleted
null=True,
related_name='courses'
)
duration = models.PositiveIntegerField(help_text="Duration in hours")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['title']
indexes = [
models.Index(fields=['title']),
models.Index(fields=['teacher']),
models.Index(fields=['category']),
]
def __str__(self):
return self.title
class Enrollment(models.Model):
"""Model representing an enrollment of a student in a course."""
student = models.ForeignKey(
User,
on_delete=models.CASCADE, # Delete enrollment when student is deleted
limit_choices_to={'user_type': 'user'},
related_name='enrollments'
)
course = models.ForeignKey(
Course,
on_delete=models.CASCADE, # Delete enrollment when course is deleted
related_name='enrollments'
)
enrollment_date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('student', 'course') # Prevent multiple enrollments in
the same course
indexes = [
models.Index(fields=['student']),
models.Index(fields=['course']),
]
def __str__(self):
return f"{self.student.username} enrolled in {self.course.title}"
-----------------------------------------------------------------------------------
myapp/authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings
class CustomJWTAuthentication(JWTAuthentication):
def authenticate(self, request):
# Get the token from the cookie
raw_token = request.COOKIES.get(settings.SIMPLE_JWT['AUTH_COOKIE'])
if not raw_token:
return None
-----------------------------------------------------------------------------------
-------
myapp/Serializer.py
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['user_id'] = user.id # Add user ID
return token
class CourseSerializer(serializers.ModelSerializer):
enrollments_count = serializers.SerializerMethodField()
teacher_username = serializers.SerializerMethodField()
class Meta:
model = Course
fields = ['id', 'title', 'description', 'video', 'technologies',
'category', 'duration', 'image', 'status',
'enrollments_count', 'teacher_username', 'teacher_id']
class EnrollmentSerializer(serializers.ModelSerializer):
student = UserSerializer()
course = CourseSerializer() # Ensure this line is included
class Meta:
model = Enrollment
fields = ['id', 'student', 'course', 'enrollment_date', 'status']
class EnrolledCourseSerializer(serializers.ModelSerializer):
teacher_name = serializers.CharField(source='teacher.full_name',
read_only=True)
class Meta:
model = Course
fields = ['id', 'title', 'teacher_name']
-----------------------------------------------------------------------------------
myapp/views.py
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
return response
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def check_auth(request):
return Response({'isAuthenticated': True})
class CourseListView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = CourseSerializer
def get_queryset(self):
return Course.objects.filter(teacher=self.request.user).annotate(
total_enrolled=Count(
'enrollments',
filter=Q(enrollments__status='active')
)
).prefetch_related('enrollments')
# Debug information
print("Number of courses:", queryset.count())
for course in queryset:
print(f"Course {course.id} - {course.title}: {course.total_enrolled}
enrollments")
return Response(serializer.data)
class CourseListView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = CourseSerializer
def get_queryset(self):
return Course.objects.filter(teacher=self.request.user).annotate(
total_enrolled=Count(
'enrollments',
filter=Q(enrollments__status='active')
)
).prefetch_related('enrollments')
# Debug information
print("Number of courses:", queryset.count())
for course in queryset:
print(f"Course {course.id} - {course.title}: {course.total_enrolled}
enrollments")
return Response(serializer.data)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def check_course_enrollments(request, course_id):
try:
course = Course.objects.get(id=course_id)
active_enrollments = Enrollment.objects.filter(course=course,
status='active').count()
return Response({
'course_id': course_id,
'title': course.title,
'active_enrollments': active_enrollments
})
except Course.DoesNotExist:
return Response({'error': 'Course not found'}, status=404)
class EnrolledCoursesView(generics.ListAPIView):
serializer_class = EnrolledCourseSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Course.objects.filter(
enrollments__student=self.request.user,
enrollments__status='active'
)
-----------------------------------------------------------------------------------
urls.py
urlpatterns = [
path('register/', UserRegisterView.as_view(), name='register'),
path('login/', CustomTokenObtainPairView.as_view(), name='login'),
path('user/<int:pk>/', UserDetailView.as_view(), name='user_detail'),
path('logout/', LogoutView.as_view(), name='logout'),
path('check-auth/', check_auth, name='check_auth'),
path('my-courses/', CourseListView.as_view(), name='my_courses'),
path('course/<int:pk>/', CourseDetailView.as_view(), name='course_detail'),
path('enrolled-courses/', EnrolledCoursesView.as_view(),
name='enrolled_courses'),
path('unenroll-course/<int:course_id>/', unenroll_course,
name='unenroll_course'),
path('check-course-enrollments/<int:course_id>/', check_course_enrollments,
name='check_course_enrollments'),
path('courses/<int:course_id>/chapters/', ChapterListCreateView.as_view(),
name='chapter-list-create'),
path('chapters/<int:pk>/', ChapterUpdateDeleteView.as_view(), name='chapter-
update-delete'),
path('all-courses/<str:tech>/', CoursesByTechListView.as_view(), name='courses-
by-tech'),
]
-----------------------------------------------------------------------------------
--
frontend
src/context/authContext.js
import React, { createContext, useState, useEffect } from 'react';
import { checkAuthStatus } from './authUtils';
checkAuth();
}, []); // Runs once on initial mount to check authentication
return (
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
{children}
</AuthContext.Provider>
);
};
src/context/authUtils.js
import axios from 'axios';
// src/context/courseService.js
import axios from '../services/axiosInstance';
src/services/axiosInstance.js
import axios from 'axios';
src/components/ProtectedRoute.js
const ProtectedRoute = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const location = useLocation();
useEffect(() => {
const verifyAuth = async () => {
// If it's a public route, we don't need to check authentication
if (isPublicRoute()) {
setIsAuthenticated(true);
setIsLoading(false);
return;
}
try {
const authStatus = await checkAuthStatus();
setIsAuthenticated(authStatus);
} catch (error) {
console.error('Error checking auth status:', error);
setIsAuthenticated(false);
} finally {
setIsLoading(false);
}
};
verifyAuth();
}, [location]);
if (isLoading) {
return <div>Loading...</div>;
}
return children;
};
src/components/CourseDetail.js
import React, { useEffect, useState } from 'react';
import { Link, useParams, useNavigate } from 'react-router-dom';
import { getCourseDetails } from '../context/courseService';
import axios from '../services/axiosInstance';
import { FaYoutube } from 'react-icons/fa';
useEffect(() => {
const fetchCourseDetails = async () => {
try {
const data = await getCourseDetails(courseId);
setCourse(data);
fetchData();
}, [courseId]);
if (!isEnrolled) {
try {
// Make sure to include the token in the Authorization header
const response = await axios.post(`/enroll-course/${courseId}/`,
{}, {
headers: {
Authorization: `Bearer ${token}`,
}
});
if (loading) return (
<div className="d-flex justify-content-center align-items-center"
style={{ height: '100vh' }}>
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
);
if (error) return (
<div className="text-center text-danger" style={{ height: '100vh' }}>
<h4>{error}</h4>
</div>
);
if (!course) return (
<div className="text-center" style={{ height: '100vh' }}>
<h4>No course found.</h4>
</div>
);
return (
<div className="container-fluid my-5">
<div className="row">
<div className="col-md-8 mx-auto">
<div className="card shadow-sm mb-4">
<div className="card-body d-flex flex-column flex-md-row">
<img
src={course.image}
alt={course.title}
className="img-fluid mb-3 mb-md-0"
style={{ maxWidth: '500px', height: 'auto',
marginRight: '20px' }}
/>
<div>
<h1 className="card-title">{course.title}</h1>
<p className="card-text">{course.description}</p>
<hr />
<strong>Teacher:</strong>
<Link to={`/teacher-detail/$
{course.teacher_id}`}>{course.teacher_username}</Link>
<p><strong>Duration:</strong> {course.duration}
hours</p>
<p><strong>Tech:</strong>
{course.technologies.split(',').map((tech,
index) => (
<Link key={index} to={`/all-courses/$
{tech.trim().toLowerCase()}`} className="btn btn-link">
{tech}
</Link>
))}
</p>
<p><strong>Total Enrolled Students:</strong>
{course.enrollments_count}</p>
{!isEnrolled ? (
<button className="btn btn-primary"
onClick={handleEnroll}>Enroll</button>
) : (
<span className="text-success">Already
Enrolled</span>
)}
</div>
</div>
</div>
{isEnrolled && (
<div className="card shadow-sm">
<h5 className="card-header text-center">Chapters
List</h5>
<div className="card-body">
{chapters.length > 0 ? (
<div className="table-responsive">
<table className="table table-striped
table-hover">
<thead>
<tr>
<th>Title</th>
<th>Duration</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{chapters.map((chapter) => (
<tr key={chapter.id}>
<td>{chapter.title}</td>
<td>
src/pages/Login.js
import React, { useState, useContext } from 'react'; // Import useContext
import { useNavigate, useLocation } from 'react-router-dom';
import { loginUser } from '../context/authService';
import { AuthContext } from '../context/AuthContext';
return (
<div className="container mt-4">
<div className="row justify-content-center">
<div className="col-12 col-md-6">
<div className="card">
<h5 className="card-header text-center">User Login</h5>
<div className="card-body">
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<div className="mb-3 text-start">
<label className="form-label">Email
Address</label>
<input
type="text"
name="username"
placeholder="Username"
onChange={handleChange}
required
className="form-control"
/>
</div>
<div className="mb-3 text-start">
<label className="form-label">Password</label>
<input
type="password"
name="password"
placeholder="Password"
onChange={handleChange}
required
className="form-control"
/>
</div>
<div className="mb-3 text-start">
<button type="submit" className="btn btn-
primary w-100 mt-4">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
};