diff --git a/FusionIIIT/Fusion/settings/development.py b/FusionIIIT/Fusion/settings/development.py index 63587a11f..053abd03f 100644 --- a/FusionIIIT/Fusion/settings/development.py +++ b/FusionIIIT/Fusion/settings/development.py @@ -1,4 +1,5 @@ from Fusion.settings.common import * +import os DEBUG = True TEMPLATE_DEBUG = True @@ -16,6 +17,8 @@ } } +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( diff --git a/FusionIIIT/applications/central_mess/migrations/0001_initial.py b/FusionIIIT/applications/central_mess/migrations/0001_initial.py index 7e80bedf5..b392627e5 100644 --- a/FusionIIIT/applications/central_mess/migrations/0001_initial.py +++ b/FusionIIIT/applications/central_mess/migrations/0001_initial.py @@ -149,17 +149,17 @@ class Migration(migrations.Migration): ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), ], ), - migrations.CreateModel( - name='Payments', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount_paid', models.IntegerField(default=0)), - ('payment_month', models.CharField(default=applications.central_mess.models.current_month, max_length=20)), - ('payment_year', models.IntegerField(default=applications.central_mess.models.current_year)), - ('payment_date', models.DateField(default=datetime.date(2024, 6, 19))), - ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), - ], - ), + # migrations.CreateModel( + # name='Payments', + # fields=[ + # ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + # ('amount_paid', models.IntegerField(default=0)), + # ('payment_month', models.CharField(default=applications.central_mess.models.current_month, max_length=20)), + # ('payment_year', models.IntegerField(default=applications.central_mess.models.current_year)), + # ('payment_date', models.DateField(default=datetime.date(2024, 6, 19))), + # ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), + # ], + # ), migrations.CreateModel( name='Mess_minutes', fields=[ diff --git a/FusionIIIT/applications/online_cms/api/serializers.py b/FusionIIIT/applications/online_cms/api/serializers.py index 93f79f445..3ade5319c 100644 --- a/FusionIIIT/applications/online_cms/api/serializers.py +++ b/FusionIIIT/applications/online_cms/api/serializers.py @@ -22,6 +22,12 @@ class Meta: model = Student fields = '__all__' +class StudentAssignmentSerializer(serializers.ModelSerializer): + upload_url = serializers.FileField(use_url=True) + class Meta: + model = StudentAssignment + fields = '__all__' + class CourseInstructorSerializer(serializers.ModelSerializer): class Meta: model = CourseInstructor @@ -50,6 +56,7 @@ class Meta: fields = '__all__' class CourseDocumentsSerializer(serializers.ModelSerializer): + document_url = serializers.FileField(use_url=True) class Meta: model = CourseDocuments fields = '__all__' diff --git a/FusionIIIT/applications/online_cms/api/urls.py b/FusionIIIT/applications/online_cms/api/urls.py index 87919db4d..07d034955 100644 --- a/FusionIIIT/applications/online_cms/api/urls.py +++ b/FusionIIIT/applications/online_cms/api/urls.py @@ -12,4 +12,28 @@ # url(r'^courses', CourseListView.as_view(), name='courses'), url(r'^(?P[A-Za-z0-9]+)/(?P[\d.]+)/$', views.course, name='course'), url(r'^courses', views.courseview, name='courseview'), + url(r'^upload-grading-scheme/', views.create_grading_scheme, name='create_grading_scheme'), + + url(r'^modules/add/$', views.add_module, name='add_module'), + url(r'^modules/delete/(?P\d+)/$', views.delete_module, name='delete_module'), + + url(r'^slides/(?P\d+)/add_document/$', views.add_slide, name='add_document'), + url(r'^slides/delete/(?P\d+)/$', views.delete_slide, name='delete_document'), + + url(r'^attendance', views.view_attendance, name='view_attendance'), + + url(r'^modules/$', views.get_modules, name='get_modules'), + url(r'^upload-attendance/', views.upload_attendance, name='upload_attendance'), + url(r'^get-attendance/', views.get_attendance, name='get_attendance'), + + url(r'^slides/$', views.get_slides, name='get_slides'), + + + url(r'^submit-marks/$', views.submit_marks, name='api_submit_marks'), + url(r'^send_notification/$', views.send_course_notification, name='send_notification'), + + url(r'^slides/$', views.get_slides, name='get_slides'), + + url(r'^submit-assignment/$', views.submit_assignment, name='submit_assignment'), + url(r'^assignments/course/(?P\d+)/$', views.get_assignments_by_course, name='get_assignments_by_course'), ] diff --git a/FusionIIIT/applications/online_cms/api/views.py b/FusionIIIT/applications/online_cms/api/views.py index 23df6aaf3..424cd9398 100644 --- a/FusionIIIT/applications/online_cms/api/views.py +++ b/FusionIIIT/applications/online_cms/api/views.py @@ -22,106 +22,301 @@ from applications.programme_curriculum.models import CourseInstructor from applications.academic_procedures.models import course_registration from applications.globals.models import ExtraInfo -# from applications.globals.models import * - -# from .forms import * -# from .helpers import create_thumbnail, semester -# from .models import * -# from .helpers import create_thumbnail, semester -# from notification.views import course_management_notif -# def viewcourses_serialized(request): -# user = request.user -# extrainfo = ExtraInfo.objects.select_related().get(user=user) - -# # If the user is a student -# if extrainfo.user_type == 'student': -# student = Student.objects.select_related('id').get(id=extrainfo) -# register = course_registration.objects.select_related().filter(student_id=student) - -# # Serialize registered courses -# registered_courses_data = serializers.serialize('json', register) - -# return JsonResponse({ -# 'user_type': 'student', -# 'registered_courses': registered_courses_data, -# }) - -# # If the user is faculty -# elif extrainfo.user_type == 'faculty': -# instructor = CourseInstructor.objects.select_related('curriculum_id').filter(instructor_id=extrainfo) -# curriculum_list = [Courses.objects.select_related().get(pk=x.course_id) for x in instructor] - -# # Serialize curriculum list -# curriculum_data = serializers.serialize('json', curriculum_list) - -# return JsonResponse({ -# 'user_type': 'faculty', -# 'curriculum_list': curriculum_data, -# }) - -# # If the user is an admin -# elif extrainfo.id == 'id_admin': -# # if request.session.get('currentDesignationSelected') != 'acadadmin': -# # return HttpResponseRedirect('/dashboard/') - -# calendar = Calendar.objects.all() -# timetable = Timetable.objects.all() - -# # Serialize calendar and timetable data -# calendar_data = serializers.serialize('json', calendar) -# timetable_data = serializers.serialize('json', timetable) - -# return JsonResponse({ -# 'user_type': 'admin', -# 'academic_calendar': calendar_data, -# 'timetable': timetable_data, -# }) - -# return JsonResponse({ -# 'error': 'Unknown user type' -# }) +from django.contrib.auth.models import User +from notifications.signals import notify +from django.shortcuts import get_object_or_404 +from rest_framework.permissions import IsAuthenticated +from rest_framework.decorators import permission_classes +from django.db import connection +from rest_framework.permissions import IsAuthenticated +from applications.online_cms.models import Attendance +import pandas as pd +from datetime import datetime from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status # Import for custom status codes from .serializers import * -# class CourseListView(APIView): -# def get(self, request): -# user = request.user - -# try: -# extrainfo = ExtraInfo.objects.select_related().get(user=user) -# except ExtraInfo.DoesNotExist: -# return Response({'message': 'ExtraInfo object not found for this user'}, status=status.HTTP_404_NOT_FOUND) - -# if extrainfo.user_type == 'student': -# try: -# student = Student.objects.select_related('id').get(id=extrainfo) -# register = course_registration.objects.select_related().filter(student_id=student) -# except (Student.DoesNotExist, course_registration.DoesNotExist): -# return Response({'message': 'No courses found for this student'}, status=status.HTTP_404_NOT_FOUND) - -# courses = collections.OrderedDict() -# for reg in register: -# # instructor = CourseInstructor.objects.select_related().get(course_id=reg.course_id).first() -# instructors = CourseInstructor.objects.select_related().filter(course_id=reg.course_id) -# instructor = instructors.first() # Get the first instructor - -# courses[reg] = instructor - -# courses_serializer = CoursesSerializer(courses, many=True) # Assuming CourseRegistrationSerializer exists -# return Response(courses_serializer.data) -# elif extrainfo.user_type == 'faculty': -# # ... similar logic for faculty courses ... - -# return Response(faculty_courses_serializer.data) # Assuming faculty_courses_serializer exists -# elif extrainfo.user_type == 'id_admin': -# # ... similar logic for admin courses ... - -# return Response(admin_courses_serializer.data) # Assuming admin_courses_serializer exists -# else: -# return Response({'message': 'Invalid user type'}, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def upload_attendance(request): + if 'file' not in request.FILES: + return JsonResponse({'error': 'No file uploaded'}, status=400) + + try: + excel_file = request.FILES['file'] + df = pd.read_excel(excel_file, keep_default_na=False) + + # Normalize columns + df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_') + + required_columns = ['student_id', 'instructor_id', 'date', 'present', 'no_of_attendance'] + missing_cols = [col for col in required_columns if col not in df.columns] + if missing_cols: + return JsonResponse({ + 'error': 'Missing required columns', + 'missing': missing_cols, + 'found': df.columns.tolist() + }, status=400) + + successes = 0 + errors = [] + + for index, row in df.iterrows(): + try: + # Get the actual values from the Excel columns + student_id = str(row['student_id']) # Changed from student_id_id + instructor_id = str(row['instructor_id']) # Changed from instructor_id_id + + if pd.isna(row['date']): + raise ValueError("Date cannot be empty") + + if isinstance(row['date'], str): + date_str = row['date'].split()[0] + try: + date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() + except ValueError: + date_obj = datetime.strptime(date_str, '%d/%m/%Y').date() + else: + date_obj = row['date'].date() + + present = int(row['present']) + no_of_attendance = int(row['no_of_attendance']) + + with connection.cursor() as cursor: + # Check if record exists + cursor.execute( + "SELECT 1 FROM online_cms_attendance WHERE student_id_id = %s AND instructor_id_id = %s AND date = %s", + [student_id, instructor_id, date_obj] + ) + exists = cursor.fetchone() + + if exists: + # Update existing record + cursor.execute( + "UPDATE online_cms_attendance SET present = %s, no_of_attendance = %s " + "WHERE student_id_id = %s AND instructor_id_id = %s AND date = %s", + [present, no_of_attendance, student_id, instructor_id, date_obj] + ) + action = 'updated' + else: + # Insert new record + cursor.execute( + "INSERT INTO online_cms_attendance (student_id_id, instructor_id_id, date, present, no_of_attendance) " + "VALUES (%s, %s, %s, %s, %s)", + [student_id, instructor_id, date_obj, present, no_of_attendance] + ) + action = 'created' + + successes += 1 + print(f"Row {index + 2}: {action} successfully") + + except Exception as e: + error_msg = f"Row {index + 2}: {str(e)}" + errors.append(error_msg) + print(f"Error in row {index + 2}:", e) + + return JsonResponse({ + 'message': f'Processed {successes} rows successfully', + 'success_count': successes, + 'error_count': len(errors), + 'errors': errors if errors else None + }) + + except Exception as e: + print("Upload failed:", str(e)) + return JsonResponse({'error': str(e)}, status=500) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_attendance(request): + try: + # Get query parameters for filtering + student_id = request.GET.get('student_id') + instructor_id = request.GET.get('instructor_id') + date_from = request.GET.get('date_from') + date_to = request.GET.get('date_to') + + # Start with all records + queryset = Attendance.objects.all() + + # Apply filters if provided + if student_id: + queryset = queryset.filter(student_id_id=student_id) + if instructor_id: + queryset = queryset.filter(instructor_id_id=instructor_id) + if date_from and date_to: + queryset = queryset.filter(date__range=[date_from, date_to]) + elif date_from: + queryset = queryset.filter(date__gte=date_from) + elif date_to: + queryset = queryset.filter(date__lte=date_to) + + # Prepare the response data + data = [] + for attendance in queryset: + data.append({ + 'id': attendance.id, + 'student_id': attendance.student_id_id, # Directly access the ID + 'instructor_id': attendance.instructor_id_id, # Directly access the ID + 'date': attendance.date.strftime('%Y-%m-%d'), + 'present': attendance.present, + 'no_of_attendance': attendance.no_of_attendance + }) + + return JsonResponse({ + 'success': True, + 'count': len(data), + 'data': data + }) + + except Exception as e: + return JsonResponse({ + 'success': False, + 'error': str(e) + }, status=500) + +@api_view(['GET']) +def get_modules(request): + if request.method == "GET": + modules = Modules.objects.all().values("id", "module_name") # Use correct field name + return JsonResponse(list(modules), safe=False) + return JsonResponse({"error": "Method not allowed"}, status=405) + +# add a module +@api_view(['POST']) +def add_module(request): + # Extract data from the request + course_id = request.data.get('course_id') + module_name = request.data.get('module_name') + + + # Ensure the course exists + try: + course = Courses.objects.get(id=course_id) + except Courses.DoesNotExist: + return Response({"error": "Course not found"}, status=status.HTTP_404_NOT_FOUND) + + # Create the module + module = Modules.objects.create(module_name=module_name, course_id=course) + + # Serialize the module data and return + serializer = ModulesSerializer(module) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + +# delete a module +@api_view(['DELETE']) +def delete_module(request, module_id): + try: + # Get the module by primary key + module = Modules.objects.get(pk=module_id) + module.delete() # Delete the module + return Response(status=status.HTTP_204_NO_CONTENT) # No content after deletion + except Modules.DoesNotExist: + return Response({"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def get_slides(request): + documents = CourseDocuments.objects.all() # Fetch all documents + serializer = CourseDocumentsSerializer(documents, many=True) + return Response(serializer.data) + +# Add a slide +@api_view(['POST']) +def add_slide(request, module_id): + try: + # Get the module using module_id from the URL + module = Modules.objects.get(id=module_id) + except Modules.DoesNotExist: + return Response({"error": "Module not found"}, status=status.HTTP_404_NOT_FOUND) + + # Extract data from the request body + document_name = request.data.get('document_name') + document_url = request.data.get('document_url') + description = request.data.get('description', '') # Default to empty string if not provided + + # Create a new CourseDocument for the slide + course_document = CourseDocuments.objects.create( + course_id=module.course_id, + module_id=module, + document_name=document_name, + document_url=document_url, + description=description + ) + + # Serialize the document data and return the response + serializer = CourseDocumentsSerializer(course_document) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + +# Delete a slide +@api_view(['DELETE']) +def delete_slide(request, slide_id): + try: + # Get the slide by primary key + slide = CourseDocuments.objects.get(pk=slide_id) + slide.delete() # Delete the slide + return Response(status=status.HTTP_204_NO_CONTENT) # No content after deletion + except CourseDocuments.DoesNotExist: + return Response({"error": "Slide not found"}, status=status.HTTP_404_NOT_FOUND) + +@api_view(['GET']) +def get_assignments_by_course(request, course_id): + try: + course = Courses.objects.get(pk=course_id) + except Courses.DoesNotExist: + return Response({"error": "Course not found"}, status=status.HTTP_404_NOT_FOUND) + + assignments = Assignment.objects.filter(course_id=course) + serializer = AssignmentSerializer(assignments, many=True) + return Response(serializer.data) + +@api_view(['POST']) +def submit_assignment(request): + student_id = request.data.get('student_id') + assignment_id = request.data.get('assignment_id') + upload_url = request.data.get('upload_url') + assign_name = request.data.get('assign_name') + + if not all([student_id, assignment_id, upload_url, assign_name]): + return Response( + {"error": "Missing one or more required fields: student_id, assignment_id, upload_url, assign_name"}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Validate foreign keys manually + try: + student = Student.objects.get(pk=student_id) + except Student.DoesNotExist: + return Response({"error": f"Student with ID {student_id} not found."}, status=404) + + try: + assignment = Assignment.objects.get(pk=assignment_id) + except Assignment.DoesNotExist: + return Response({"error": f"Assignment with ID {assignment_id} not found."}, status=404) + + # Prevent duplicate + if StudentAssignment.objects.filter(student_id=student, assignment_id=assignment).exists(): + return Response( + {"error": "You have already submitted this assignment."}, + status=400 + ) + + student_assignment = StudentAssignment.objects.create( + student_id=student, + assignment_id=assignment, + upload_url=upload_url, + assign_name=assign_name + ) + + serializer = StudentAssignmentSerializer(student_assignment) + return Response(serializer.data, status=201) + @api_view(['GET']) def courseview(request): @@ -537,4 +732,286 @@ def course(request, course_code, version): "grading_scheme":gradingscheme_serialiser.data, } return Response(data,status=status.HTTP_200_OK) - \ No newline at end of file + + +#Grading Scheme + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def create_grading_scheme(request): + """ + API to create/update grading scheme. + - Only faculty users can access this API. + - Accepts all required data in the request body. + """ + + user = request.user + + # Check if user is faculty + extrainfo = get_object_or_404(ExtraInfo, user=user) + if extrainfo.user_type != 'faculty': + return Response({"error": "Only faculty can create grading schemes"}, status=status.HTTP_403_FORBIDDEN) + + # Extract request data + course_code = request.data.get("course_code") + version = request.data.get("version") + evaluations = request.data.get("evaluations", {}) + grading_boundaries = request.data.get("grading_boundaries", {}) + + # Validate course existence + course = get_object_or_404(Courses, code=course_code, version=version) + + # ✅ Save Grading Scheme (Evaluations) + for eval_type, weightage in evaluations.items(): + GradingScheme.objects.update_or_create( + course_id=course, + type_of_evaluation=eval_type, + defaults={"weightage": weightage} + ) + + # ✅ Save Grading Boundaries + GradingScheme_grades.objects.update_or_create( + course_id=course, + defaults=grading_boundaries + ) + + return Response({"message": "Grading scheme created successfully"}, status=status.HTTP_201_CREATED) + + +@api_view(['GET']) +def view_attendance(request, course_code, version): + user = request.user + + if not course_code or not version: + return Response({"error": "course_code and version are required parameters"}, status=status.HTTP_400_BAD_REQUEST) + + try: + extrainfo = ExtraInfo.objects.select_related().get(user=user) + except ExtraInfo.DoesNotExist: + return Response({"error": "User information not found"}, status=status.HTTP_404_NOT_FOUND) + + if extrainfo.user_type == 'student': + try: + student = Student.objects.select_related('id').get(id=extrainfo) + course = Courses.objects.select_related().get(code=course_code, version=version) + instructor = CourseInstructor.objects.select_related().get(course_id=course, batch_id=student.batch_id) + print(instructor) + print(student) + print(course) + except (Student.DoesNotExist, Courses.DoesNotExist, CourseInstructor.DoesNotExist): + return Response({"error": "Course or instructor not found"}, status=status.HTTP_404_NOT_FOUND) + + attendance_records = Attendance.objects.select_related().filter(student_id=student, instructor_id=instructor) + attendance_serializer = AttendanceSerializer(attendance_records, many=True) + + return Response(attendance_serializer.data, status=status.HTTP_200_OK) + + elif extrainfo.user_type == 'instructor': + try: + instructor = CourseInstructor.objects.select_related('course_id').get(instructor_id=extrainfo, course_id__code=course_code, course_id__version=version) + except CourseInstructor.DoesNotExist: + return Response({"error": "Instructor or course not found"}, status=status.HTTP_404_NOT_FOUND) + + registered_students = course_registration.objects.select_related('student_id').filter(course_id=instructor.course_id) + attendance_records = Attendance.objects.select_related().filter(instructor_id=instructor, student_id__in=[stu.student_id for stu in registered_students]) + attendance_serializer = AttendanceSerializer(attendance_records, many=True) + + return Response(attendance_serializer.data, status=status.HTTP_200_OK) + + return Response({"error": "Invalid user type"}, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def submit_marks(request): + """ + API to submit/update student marks for evaluations. + - Only faculty users can access this API. + - Accepts student marks data in batch or individual format. + """ + user = request.user + + # Check if user is faculty + extrainfo = get_object_or_404(ExtraInfo, user=user) + if extrainfo.user_type != 'faculty': + return Response({"error": "Only faculty can submit marks"}, status=status.HTTP_403_FORBIDDEN) + + # Extract request data + course_code = request.data.get("course_code") + version = request.data.get("version") + evaluation_type = request.data.get("evaluation_type") # e.g., "midsem", "endsem", "assignment1" + marks_data = request.data.get("marks_data", []) # List of student marks + + if not course_code or not version or not evaluation_type or not marks_data: + return Response({"error": "Missing required fields"}, status=status.HTTP_400_BAD_REQUEST) + + # Validate course existence + try: + course = Courses.objects.get(code=course_code, version=version) + except Courses.DoesNotExist: + return Response({"error": f"Course {course_code} (version {version}) not found"}, + status=status.HTTP_404_NOT_FOUND) + + # Check if faculty teaches this course + instructor = CourseInstructor.objects.filter(instructor_id=extrainfo, course_id=course).first() + if not instructor: + return Response({"error": "You are not an instructor for this course"}, + status=status.HTTP_403_FORBIDDEN) + + # Process marks data + successful_updates = 0 + errors = [] + + for entry in marks_data: + try: + # Get required fields + student_id = entry.get("student_id") + marks = entry.get("marks") + + if not student_id or marks is None: + errors.append(f"Missing student_id or marks in entry: {entry}") + continue + + # Validate student exists and is registered for the course + try: + student_extrainfo = ExtraInfo.objects.get(id=student_id) + student = Student.objects.get(id=student_extrainfo) + + # Check if student is registered for this course + registration = course_registration.objects.filter( + student_id=student, + course_id=course + ).exists() + + if not registration: + errors.append(f"Student {student_id} is not registered for this course") + continue + + except (ExtraInfo.DoesNotExist, Student.DoesNotExist): + errors.append(f"Student with ID {student_id} not found") + continue + + # Create or update marks in Student_grades model + defaults = { + "marks": marks, + "instructor_id": extrainfo + } + + # Optional comment field + if "comment" in entry: + defaults["comment"] = entry.get("comment") + + # Update or create the marks entry + Student_grades.objects.update_or_create( + student_id=student, + course_id=course, + exam_type=evaluation_type, + defaults=defaults + ) + + successful_updates += 1 + + except Exception as e: + errors.append(f"Error processing entry for student {entry.get('student_id')}: {str(e)}") + + # Return response with results + response_data = { + "message": f"Processed {successful_updates} mark entries successfully", + "successful_updates": successful_updates, + "error_count": len(errors) + } + + if errors: + response_data["errors"] = errors + + return Response(response_data, status=status.HTTP_200_OK if successful_updates > 0 else status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def send_course_notification(request): + """ + API to send course management notifications. + + POST parameters: + - recipient_id: User ID of the recipient + - type: Type of notification (e.g., 'new_slide', 'new_assignment', etc.) + - course_code: (Optional) Code of the course + - message: (Optional) Custom message for custom notifications + """ + try: + # Extract data from request + recipient_id = request.data.get('recipient_id') + notification_type = request.data.get('type') + course_code = request.data.get('course_code') + message = request.data.get('message') + + # Validate required fields + if not recipient_id or not notification_type: + return Response( + {"error": "recipient_id and type are required fields"}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Get the recipient user + try: + recipient = User.objects.get(id=recipient_id) + except User.DoesNotExist: + return Response( + {"error": f"Recipient with ID {recipient_id} not found"}, + status=status.HTTP_404_NOT_FOUND + ) + + # Send notification + sender = request.user # Current logged-in user is the sender + course_management_notif( + sender=sender, + recipient=recipient, + type=notification_type, + course_code=course_code, + message=message + ) + + return Response( + {"message": "Notification sent successfully"}, + status=status.HTTP_200_OK + ) + + except Exception as e: + return Response( + {"error": f"Failed to send notification: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + +def course_management_notif(sender, recipient, type, course_code=None, message=None): + """ + Function to handle course management notifications. + + @param: + sender - User sending the notification + recipient - User receiving the notification + type - Type of notification (e.g., 'new_slide', 'new_assignment', etc.) + course_code - Code of the course (optional) + message - Custom message (optional) + """ + url = 'online_cms:course' # URL to redirect to when notification is clicked + module = 'Course Management' + sender = sender + recipient = recipient + verb = '' + + # Define different notification messages based on type + if type == 'new_slide': + verb = f"New slide has been uploaded for course {course_code}" if course_code else "New slide has been uploaded" + elif type == 'new_assignment': + verb = f"New assignment has been posted for course {course_code}" if course_code else "New assignment has been posted" + elif type == 'grade_updated': + verb = f"Your grades have been updated for course {course_code}" if course_code else "Your grades have been updated" + elif type == 'assignment_feedback': + verb = f"Feedback added to your assignment for course {course_code}" if course_code else "Feedback added to your assignment" + elif type == 'attendance_updated': + verb = f"Your attendance has been updated for course {course_code}" if course_code else "Your attendance has been updated" + elif type == 'custom': + # For custom notifications + verb = message if message else "You have a new notification" + + # Send the notification + notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) \ No newline at end of file diff --git a/FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py b/FusionIIIT/applications/online_cms/migrations/0002_auto_20250413_1458.py similarity index 86% rename from FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py rename to FusionIIIT/applications/online_cms/migrations/0002_auto_20250413_1458.py index 659201064..49e4c370d 100644 --- a/FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py +++ b/FusionIIIT/applications/online_cms/migrations/0002_auto_20250413_1458.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.5 on 2024-10-23 10:02 +# Generated by Django 3.1.5 on 2025-04-13 14:58 from django.db import migrations, models import django.db.models.deletion @@ -7,6 +7,7 @@ class Migration(migrations.Migration): dependencies = [ + ('globals', '0004_extrainfo_last_selected_role'), ('academic_information', '0001_initial'), ('online_cms', '0001_initial'), ] @@ -36,6 +37,11 @@ class Migration(migrations.Migration): name='verified', field=models.BooleanField(default=False), ), + migrations.AlterField( + model_name='attendance', + name='instructor_id', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='globals.faculty'), + ), migrations.AlterField( model_name='attendance', name='present', diff --git a/FusionIIIT/applications/online_cms/models.py b/FusionIIIT/applications/online_cms/models.py index 98d68b24c..5aa7e8ca5 100644 --- a/FusionIIIT/applications/online_cms/models.py +++ b/FusionIIIT/applications/online_cms/models.py @@ -2,7 +2,7 @@ #import models used from academic procedure and academic information modules and globals from applications.academic_information.models import Student, Curriculum from applications.programme_curriculum.models import Course as Courses, CourseInstructor -from applications.globals.models import ExtraInfo +from applications.globals.models import ExtraInfo, Faculty #the modules for containing course content class Modules(models.Model): @@ -19,7 +19,7 @@ class CourseDocuments(models.Model): upload_time = models.DateTimeField(auto_now=True) description = models.CharField(max_length=100) document_name = models.CharField(max_length=40) - document_url = models.CharField(max_length=100, null=True) + document_url = models.FileField(upload_to='course_documents/') def __str__(self): return '{} - {}'.format(self.course_id, self.document_name) @@ -173,7 +173,7 @@ class StudentAssignment(models.Model): student_id = models.ForeignKey(Student, on_delete=models.CASCADE) assignment_id = models.ForeignKey(Assignment, on_delete=models.CASCADE) upload_time = models.DateTimeField(auto_now=True) - upload_url = models.TextField(max_length=200) + upload_url = models.FileField(upload_to='assignments/') score = models.IntegerField(null=True) #score is submitted by faculty feedback = models.CharField(max_length=100, null=True) #feedback by the faculty for the solution of the assignment submitted assign_name = models.CharField(max_length=100) @@ -279,7 +279,7 @@ class Attendance(models.Model): student_id = models.ForeignKey(Student,on_delete=models.CASCADE) # course_id = models.ForeignKey(Course) # attend = models.CharField(max_length=6, choices=Constants.ATTEND_CHOICES) - instructor_id = models.ForeignKey(CourseInstructor, on_delete=models.CASCADE) + instructor_id = models.ForeignKey(Faculty, on_delete=models.CASCADE) # curriculum_id = models.ForeignKey(Curriculum, on_delete=models.CASCADE) date = models.DateField() present = models.IntegerField(default=0)