0% found this document useful (0 votes)
16 views15 pages

Data 10

Uploaded by

savepasa1
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views15 pages

Data 10

Uploaded by

savepasa1
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 15

when i click on enroll button in Course detail page it send me to the login page.

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)

# New field to track the status of the course


STATUS_CHOICES = (
('active', 'Active'),
('inactive', 'Inactive'),
('completed', 'Completed'),
)
status = models.CharField(max_length=10, choices=STATUS_CHOICES,
default='active')
image = models.ImageField(upload_to='course_images/', null=True, blank=True) #
New field for course image

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)

# New field to track the status of the enrollment


STATUS_CHOICES = (
('active', 'Active'),
('completed', 'Completed'),
('dropped', 'Dropped'),
)
status = models.CharField(max_length=10, choices=STATUS_CHOICES,
default='active')

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

# Add the token to the authorization header


validated_token = self.get_validated_token(raw_token)
return self.get_user(validated_token), validated_token

def get_header(self, request):


# Override to check cookies first, then fall back to Authorization header
raw_token = request.COOKIES.get(settings.SIMPLE_JWT['AUTH_COOKIE'])
if raw_token:
return f"Bearer {raw_token}".encode()
return super().get_header(request)

-----------------------------------------------------------------------------------
-------
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

def validate(self, attrs):


data = super().validate(attrs)
data['user_id'] = self.user.id # Include user ID in the response
data['user_type'] = self.user.user_type # Include user type
return data

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']

def get_enrollments_count(self, obj):


return getattr(obj, 'total_enrolled', Enrollment.objects.filter(course=obj,
status='active').count())

def get_teacher_username(self, obj):


return obj.teacher.username if obj.teacher else 'N/A'

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

def post(self, request, *args, **kwargs):


response = super().post(request, *args, **kwargs)
token = response.data['access']

# Set the token in a cookie


response.set_cookie(
key='access_token',
value=token,
httponly=True, # Prevent JavaScript access
secure=False, # Use True if running on HTTPS
samesite='Lax' # Adjust based on your needs
)

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')

def list(self, request, *args, **kwargs):


queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)

# 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')

def list(self, request, *args, **kwargs):


queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)

# 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';

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {


const [isAuthenticated, setIsAuthenticated] = useState(false);

// Check authentication status on component mount


useEffect(() => {
const checkAuth = async () => {
const authenticated = await checkAuthStatus();
setIsAuthenticated(authenticated);
};

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';

export const checkAuthStatus = async () => {


try {
const response = await axios.get('http://localhost:8000/api/check-auth/', {
withCredentials: true
});
return response.data.isAuthenticated;
} catch (error) {
console.error('Auth check failed:', error);
if (error.response && error.response.status === 401) {
// Unauthorized, user is not authenticated
return false;
}
// For other errors, we might want to throw to be handled by the caller
throw error;
}
};

// src/context/courseService.js
import axios from '../services/axiosInstance';

export const getAllCourses = async () => {


const response = await axios.get('all-courses/');
return response.data;
};

export const getMyCourses = async () => {


const response = await axios.get('my-course/');
return response.data;
};

export const deleteCourse = async (courseId) => {


await axios.delete(`delete-course/${courseId}/`);
};

export const getCourseDetails = async (courseId) => {


const response = await axios.get(`course/${courseId}/`); // Use 'course'
instead of 'course-detail'
return response.data;
};

export const updateCourse = async (courseId, courseData) => {


const formData = new FormData();
formData.append('title', courseData.title);
formData.append('description', courseData.description);
if (courseData.video) {
formData.append('video', courseData.video);
}
formData.append('technologies', courseData.technologies);
if (courseData.image) {
formData.append('image', courseData.image);
}

await axios.put(`update-course/${courseId}/`, formData, {


headers: {
'Content-Type': 'multipart/form-data',
},
});
};

export const getMyUsers = async () => {


const response = await axios.get('my-users/');
console.log('Fetched users data:', response.data); // Log the response data
return response.data;
};

export const deleteEnrollment = async (enrollmentId) => {


try {
const response = await axios.delete(`delete-enrollment/${enrollmentId}/`);
return response.data;
} catch (error) {
console.error('Error deleting enrollment:', error);
throw error;
}
};

export const getEnrolledCourses = async () => {


const response = await axios.get('enrolled-courses/');
return response.data;
};

export const unenrollCourse = async (courseId) => {


await axios.delete(`unenroll-course/${courseId}/`);
};

export const getCoursesByTech = async (tech) => {


try {
const response = await axios.get(`/all-courses/${tech}/`); // Updated to
match the new route
return response.data;
} catch (error) {
console.error('Error fetching courses by tech:', error);
throw error; // Rethrow so the calling component can handle it
}
};

src/services/axiosInstance.js
import axios from 'axios';

const instance = axios.create({


baseURL: 'http://localhost:8000/api/',
withCredentials: true,
headers: {
'Content-Type': 'application/json',
}
});

// Add response interceptor


instance.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// Clear local storage and redirect to login
localStorage.clear();
window.location.href = '/login';
}
return Promise.reject(error);
}
);

// Make sure to export the instance as default


export default instance;

import React, { useState, useEffect } from 'react';


import { Navigate, useLocation } from 'react-router-dom';
import { checkAuthStatus } from '../context/authUtils';

// List of public routes that don't require authentication


const PUBLIC_ROUTES = [
'/course-detail', // Base path for course details
'/all-courses',
'/popular-courses',
'/about',
'/teacher-detail',
'/all-courses',

// Add other public routes as needed


];

src/components/ProtectedRoute.js
const ProtectedRoute = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const location = useLocation();

// Check if the current path is public


const isPublicRoute = () => {
return PUBLIC_ROUTES.some(route => location.pathname.startsWith(route));
};

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>;
}

// If it's a public route, always render the children


if (isPublicRoute()) {
return children;
}

// For protected routes, check authentication


if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}

return children;
};

export default ProtectedRoute;

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';

const CourseDetail = () => {


const { courseId } = useParams();
const navigate = useNavigate();
const [course, setCourse] = useState(null);
const [chapters, setChapters] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isEnrolled, setIsEnrolled] = useState(false);
const [showModal, setShowModal] = useState(false);
const [selectedVideo, setSelectedVideo] = useState(null);

useEffect(() => {
const fetchCourseDetails = async () => {
try {
const data = await getCourseDetails(courseId);
setCourse(data);

// Only check enrollment if the user is logged in


const token = localStorage.getItem('authToken');
if (token) {
checkEnrollmentStatus(courseId);
}
} catch (err) {
setError('Failed to load course details.');
console.error(err);
}
};

const fetchChapters = async () => {


try {
const response = await axios.get(`courses/${courseId}/chapters/`);
setChapters(response.data);
} catch (error) {
console.error('Error fetching chapters:', error);
}
};

const checkEnrollmentStatus = async (courseId) => {


try {
const response = await axios.get(`/check-course-enrollments/$
{courseId}/`);
setIsEnrolled(response.data.active_enrollments > 0);
} catch (error) {
console.error('Error checking enrollment status:', error);
// Handle any error during enrollment check gracefully
}
};

const fetchData = async () => {


await Promise.all([fetchCourseDetails(), fetchChapters()]);
setLoading(false);
};

fetchData();
}, [courseId]);

const handleEnroll = async () => {


const token = localStorage.getItem('authToken');
if (!token) {
// If not authenticated, redirect to login
navigate('/login');
return;
}

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}`,
}
});

// Assuming the response will be a success message


setIsEnrolled(true); // Update the enrollment status
alert('You have been successfully enrolled!');
console.log(response)
} catch (error) {
console.error('Error enrolling in the course:', error);
alert('There was an error enrolling in this course.');
}
}
};

const handleVideoClick = (videoUrl) => {


const token = localStorage.getItem('authToken');
if (!token) {
navigate('/login');
return;
}
setSelectedVideo(videoUrl);
setShowModal(true);
};

const closeModal = () => {


setShowModal(false);
setSelectedVideo(null);
};

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>

{chapter.duration_hours} Hours {chapter.duration_minutes} Minutes


</td>
<td>
{chapter.video && (
<button
className="btn
btn-link text-danger"
onClick={() =>
handleVideoClick(chapter.video)}
>
<FaYoutube
size={24} />
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="text-center text-muted">No
chapters added yet.</div>
)}
</div>
</div>
)}
</div>
</div>

<div className={`modal fade ${showModal ? 'show' : ''}`}


style={{ display: showModal ? 'block' : 'none' }} tabIndex="-1" role="dialog">
<div className="modal-dialog modal-lg" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Video</h5>
<button type="button" className="close"
onClick={closeModal}>
<span>&times;</span>
</button>
</div>
<div className="modal-body">
{selectedVideo && (
<video controls className="w-100">
<source src={selectedVideo} type="video/mp4" />
Your browser does not support the video tag.
</video>
)}
</div>
</div>
</div>
</div>
</div>
);
};

export default CourseDetail;

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';

const Login = () => {


const [credentials, setCredentials] = useState({ username: '', password: '' });
const [error, setError] = useState(null);
const navigate = useNavigate();
const location = useLocation();

const handleChange = (e) => {


setCredentials({ ...credentials, [e.target.name]: e.target.value });
};

// Use useContext to get setIsAuthenticated


const { setIsAuthenticated } = useContext(AuthContext);

const handleSubmit = async (e) => {


e.preventDefault();
try {
const response = await loginUser(credentials);
const userId = response.user_id;
const userType = response.user_type;
setIsAuthenticated(true); // Update authentication state

let from = location.state?.from?.pathname || '/';


if (userType === 'user') {
from = `/user-dashboard/${userId}`;
} else if (userType === 'teacher') {
from = `/teacher-dashboard/${userId}`;
} else {
console.error('User type not recognized');
setError('User type not recognized');
return;
}

navigate(from, { replace: true });


} catch (error) {
console.error('Login error:', error);
setError('Login failed. Please check your credentials and try again.');
}
};

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>
);
};

export default Login;

You might also like