diff --git a/FusionIIIT/Fusion/settings/common.py b/FusionIIIT/Fusion/settings/common.py index d9017dd40..ee1618a24 100644 --- a/FusionIIIT/Fusion/settings/common.py +++ b/FusionIIIT/Fusion/settings/common.py @@ -284,3 +284,8 @@ CORS_ORIGIN_ALLOW_ALL = True ALLOW_PASS_RESET = True + +# session settings +SESSION_COOKIE_AGE = 15 * 60 +SESSION_EXPIRE_AT_BROWSER_CLOSE = True +SESSION_SAVE_EVERY_REQUEST = True \ No newline at end of file diff --git a/FusionIIIT/applications/academic_information/forms.py b/FusionIIIT/applications/academic_information/forms.py index ddf7ddcdc..8fde1e589 100755 --- a/FusionIIIT/applications/academic_information/forms.py +++ b/FusionIIIT/applications/academic_information/forms.py @@ -1,6 +1,6 @@ from django import forms -from .models import Exam_timetable, Meeting, Timetable +from .models import Exam_timetable, Meeting, Timetable, Student class MinuteForm(forms.ModelForm): @@ -49,3 +49,15 @@ class ExamTimetableForm(forms.ModelForm): class Meta: model = Exam_timetable fields = ('programme', 'exam_time_table',) + + +class PreRegistrationSearchForm(forms.Form): + roll_no = forms.CharField( + max_length=20, + widget=forms.TextInput(attrs={'class': 'ui input', 'placeholder': 'Enter Roll Number'}), + label="Roll Number" + ) + semester_no = forms.IntegerField( + widget=forms.NumberInput(attrs={'class': 'ui input', 'placeholder': 'Enter Semester No'}), + label="Semester No" + ) \ No newline at end of file diff --git a/FusionIIIT/applications/academic_information/urls.py b/FusionIIIT/applications/academic_information/urls.py index ea984242e..3e8c2eecb 100755 --- a/FusionIIIT/applications/academic_information/urls.py +++ b/FusionIIIT/applications/academic_information/urls.py @@ -43,4 +43,5 @@ url(r'^api/',include('applications.academic_information.api.urls')), url(r'^view_all_student_data', views.view_all_student_data, name='view_all_student_data'), url(r'^generateStudentSheet$',views.generatestudentxlsheet, name = 'generatestudentxlsheet'), + url(r'^course_allocated_students$',views.get_excel, name = 'course_allocated_students'), ] diff --git a/FusionIIIT/applications/academic_information/utils.py b/FusionIIIT/applications/academic_information/utils.py new file mode 100644 index 000000000..49e2e75f5 --- /dev/null +++ b/FusionIIIT/applications/academic_information/utils.py @@ -0,0 +1,165 @@ +from applications.academic_information.models import (Calendar, Student,Curriculum_Instructor, Curriculum, + Student_attendance) +from ..academic_procedures.models import (BranchChange, CoursesMtech, InitialRegistration, StudentRegistrationChecks, + Register, Thesis, FinalRegistration, ThesisTopicProcess, + Constants, FeePayments, TeachingCreditRegistration, SemesterMarks, + MarkSubmissionCheck, Dues,AssistantshipClaim, MTechGraduateSeminarReport, + PhDProgressExamination,CourseRequested, course_registration, MessDue, Assistantship_status , backlog_course,) + +from applications.programme_curriculum.models import(Course,CourseSlot,Batch,Semester) +from django.http import HttpResponse, JsonResponse +from django.utils import timezone +from django.core import serializers +from django.db.models import Q +import datetime +import random +from django.db import transaction +time = timezone.now() +def check_for_registration_complete (request): + batch = int(request.POST.get('batch')) + sem = int(request.POST.get('sem')) + year = request.POST.get('year') + + + date = time.date() + + try: + + pre_registration_date = Calendar.objects.all().filter(description=f"Pre Registration {sem} {year}").first() + prd_start_date = pre_registration_date.from_date + prd_end_date = pre_registration_date.to_date + + if date=prd_start_date and date<=prd_end_date: + return JsonResponse({'status':-1 , "message":"registration is under process"}) + + if FinalRegistration.objects.filter(Q(semester_id__semester_no = sem) & Q(student_id__batch = batch)).exists() : + return JsonResponse({'status':2,"message":"courses already allocated"}) + + return JsonResponse({"status":1 , "message" : "courses not yet allocated"}) + except : + return JsonResponse({"status":-3, "message" : "No such registration found"}) + +@transaction.atomic +def random_algo(batch,sem,year,course_slot) : + unique_course = InitialRegistration.objects.filter(Q(semester_id__semester_no = sem) & Q( course_slot_id__name = course_slot ) & Q(student_id__batch = batch)).values_list('course_id',flat=True).distinct() + max_seats={} + seats_alloted = {} + present_priority = {} + next_priority = {} + total_seats = 0 + for course in unique_course : + max_seats[course] = Course.objects.get(id=course).max_seats + total_seats+=max_seats[course] + seats_alloted[course] = 0 + present_priority[course] = [] + next_priority[course] = [] + + priority_1 = InitialRegistration.objects.filter(Q(semester_id__semester_no = sem) & Q( course_slot_id__name = course_slot ) & Q(student_id__batch = batch) & Q(priority=1)) + rem=len(priority_1) + if rem > total_seats : + return -1 + + for p in priority_1 : + present_priority[p.course_id.id].append([p.student_id.id.id,p.course_slot_id.id]) + with transaction.atomic() : + p_priority = 1 + while rem > 0 : + for course in present_priority : + while(len(present_priority[course])) : + random_student_selected = random.choice(present_priority[course]) + + present_priority[course].remove(random_student_selected) + + if seats_alloted[course] < max_seats[course] : + stud = Student.objects.get(id__id = random_student_selected[0]) + curriculum_object = Student.objects.get(id__id = random_student_selected[0]).batch_id.curriculum + course_object = Course.objects.get(id=course) + course_slot_object = CourseSlot.objects.get(id = random_student_selected[1]) + semester_object = Semester.objects.get(Q(semester_no = sem) & Q(curriculum = curriculum_object)) + FinalRegistration.objects.create( + student_id = stud, + verified=False, + semester_id = semester_object, + course_id = course_object, + course_slot_id = course_slot_object + ) + seats_alloted[course] += 1 + rem-=1 + else : + next = InitialRegistration.objects.get(Q(student_id__id__id = random_student_selected[0]) & Q( course_slot_id__name = course_slot ) & Q(semester_id__semester_no = sem) & Q(student_id__batch = batch) & Q(priority=p_priority+1)) + next_priority[next.course_id.id].append([next.student_id.id.id,next.course_slot_id.id]) + p_priority+=1 + present_priority = next_priority + next_priority = {course : [] for course in unique_course} + + return 1 + +@transaction.atomic +def allocate(request) : + batch = request.POST.get('batch') + sem = request.POST.get('sem') + year = request.POST.get('year') + unique_course_slot = InitialRegistration.objects.filter(Q(semester_id__semester_no = sem) & Q(student_id__batch = batch)).values('course_slot_id', 'registration_type').distinct() + unique_course_name = [] + try: + with transaction.atomic() : + for entry in unique_course_slot : + course_slot_object = CourseSlot.objects.get(id=entry['course_slot_id']) + print(course_slot_object) + if course_slot_object.type != "Open Elective": + # Fetch students registered in this course slot + students = InitialRegistration.objects.filter( + Q(semester_id__semester_no=sem) & + Q(course_slot_id=course_slot_object) & + Q(student_id__batch=batch) + ).values_list('student_id', flat=True) + + # Allocate each student directly to FinalRegistration + for student_id in students: + student = Student.objects.get(id=student_id) + semester = Semester.objects.get(semester_no=sem, curriculum=student.batch_id.curriculum) + print(semester.id) + # course = Course.objects.get(id=course_slot_object.courses.id) + course_id = course_slot_object.courses.values_list('id', flat=True).first() + # Retrieve the Course instance + course = Course.objects.get(id=course_id) + + # Insert directly into FinalRegistration + FinalRegistration.objects.create( + student_id=student, + verified=False, + semester_id=semester, + course_id=course, + course_slot_id=course_slot_object, + registration_type=entry['registration_type'] + ) + + unique_course_name.append(course_slot_object.name) + elif course_slot_object.type == "Open Elective": # Runs only for open elective course slots + if course_slot_object.name not in unique_course_name: + stat = random_algo(batch,sem,year,course_slot_object.name) + unique_course_name.append(course_slot_object.name) + if(stat == -1) : + print(course_slot_object.name) + raise Exception("seats not enough for course_slot"+str(course_slot_object.name)) + + return JsonResponse({'status': 1 , 'message' : "course allocation successful"}) + except: + return JsonResponse({'status': -1 , 'message' : "seats not enough for some course_slot"}) + +def view_alloted_course(request) : + batch = request.POST.get('batch') + sem = request.POST.get('sem') + verified = request.POST.get('year') + course = request.POST.get('course') + + registrations = FinalRegistration.objects.filter(Q(student_id__batch = batch) & Q(semester_id__semester_no = sem) & Q(course_id__code = course)) + return_list = [] + for registration in registrations: + obj = { + 'student':registration.student_id.id.id + } + return_list.append(obj) + return JsonResponse({'status':1 , 'student_list':return_list }) \ No newline at end of file diff --git a/FusionIIIT/applications/academic_information/views.py b/FusionIIIT/applications/academic_information/views.py index 2fab71e2f..c468c97c5 100755 --- a/FusionIIIT/applications/academic_information/views.py +++ b/FusionIIIT/applications/academic_information/views.py @@ -16,12 +16,13 @@ from django.views.decorators.csrf import csrf_exempt from django.template.loader import render_to_string from django.contrib.auth.decorators import login_required +from django.contrib import messages -from applications.academic_procedures.models import MinimumCredits, Register, InitialRegistration, course_registration, AssistantshipClaim,Assistantship_status +from applications.academic_procedures.models import MinimumCredits, Register, InitialRegistration, course_registration, AssistantshipClaim,Assistantship_status,FinalRegistration, StudentRegistrationChecks from applications.globals.models import (Designation, ExtraInfo, HoldsDesignation, DepartmentInfo) -from .forms import AcademicTimetableForm, ExamTimetableForm, MinuteForm +from .forms import AcademicTimetableForm, ExamTimetableForm, MinuteForm, PreRegistrationSearchForm from .models import (Calendar, Course, Exam_timetable, Grades, Curriculum_Instructor,Constants, Meeting, Student, Student_attendance, Timetable,Curriculum) from applications.programme_curriculum.models import (CourseSlot, Course as Courses, Batch, Semester, Programme, Discipline) @@ -31,6 +32,7 @@ from applications.academic_procedures.views import acad_proced_global_context , get_sem_courses from applications.programme_curriculum.models import Batch from django.db.models import Q +from .utils import check_for_registration_complete,allocate,view_alloted_course @login_required @@ -128,6 +130,7 @@ def get_context(request): assistant_flag ="" hod_flag = "" account_flag = "" + PreRegistrationsrchform = PreRegistrationSearchForm() for obj in assis_stat: assistant_flag = obj.student_status @@ -177,6 +180,7 @@ def get_context(request): 'hod_flag' : hod_flag, 'account_flag' : account_flag, 'notifications': notifs, + 'preregistrationsrchform': PreRegistrationsrchform, } return context @@ -214,8 +218,50 @@ def homepage(request): """ if user_check(request): return HttpResponseRedirect('/academic-procedures/') - + context = get_context(request) + + if request.method == "POST": + if 'check_allocation' in request.POST : + return check_for_registration_complete(request) + if 'start_allocation' in request.POST : + return allocate(request) + if 'view_allocation' in request.POST : + return view_alloted_course(request) + if 'search_preregistration' in request.POST or 'delete_preregistration' in request.POST: + form = PreRegistrationSearchForm(request.POST) + if form.is_valid(): + roll_no = form.cleaned_data['roll_no'].upper() + semester_no = form.cleaned_data['semester_no'] + print(roll_no, semester_no) + + # Fetch student object by roll number + # student = get_object_or_404(Student, id=roll_no) + + # Fetch semester by semester number + # semester = get_object_or_404(Semester, semester_no=semester_no) + # print(f"Student -> {student}") + + # Search for all initial registrations and student registration check + initial_registrations = InitialRegistration.objects.filter( + student_id_id=roll_no, semester_id__semester_no=semester_no + ) + student_registration_check = StudentRegistrationChecks.objects.filter( + student_id_id=roll_no, semester_id__semester_no=semester_no + ).first() + if ('delete_preregistration' in request.POST): + print(initial_registrations, student_registration_check) + try: + initial_registrations.delete() + student_registration_check.delete() + messages.success(request, "Student's pre registration data successfully deleted.") + except: + messages.error(request, "An error occured while deleting.") + context['delete_preregistration'] = True + else : + context['initial_registrations'] = initial_registrations + context['student_registration_check'] = student_registration_check + context['delete_preregistration'] = True return render(request, "ais/ais.html", context) @@ -1141,7 +1187,52 @@ def generate_preregistration_report(request): st = 'attachment; filename = ' + batch.name + batch.discipline.acronym + str(batch.year) + '-preresgistration.xlsx' response['Content-Disposition'] = st return response + +@login_required +def get_excel(request): + batch = request.POST.get('batch-check-view') + sem = request.POST.get('semester-check-view') + year = request.POST.get('year-check-view') + course = request.POST.get('Course-check-view') + registrations = FinalRegistration.objects.filter(Q(student_id__batch = batch) & Q(semester_id__semester_no = sem) & Q(course_id__code = course)) + return_list = [] + for registration in registrations: + return_list.append(registration.student_id.id.id) + + return_list.sort() + output = BytesIO() + book = Workbook(output,{'in_memory':True}) + title = book.add_format({'bold': True, + 'font_size': 22, + 'align': 'center', + 'valign': 'vcenter'}) + subtitle = book.add_format({'bold': True, + 'font_size': 15, + 'align': 'center', + 'valign': 'vcenter'}) + normaltext = book.add_format({'bold': False, + 'font_size': 15, + 'align': 'center', + 'valign': 'vcenter'}) + sheet = book.add_worksheet() + sheet.set_default_row(25) + sheet.write_string('A1','Student Roll no',subtitle) + sheet.write_string('B1','Student name',subtitle) + k=2 + for no in return_list : + student= User.objects.get(username=no) + sheet.write_string('A'+str(k),no,normaltext) + sheet.write_string('B'+str(k),student.first_name+student.last_name,normaltext) + k+=1 + + book.close() + output.seek(0) + + response = HttpResponse(output.read(),content_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = 'attachment; filename='+ course +'_student_list.xlsx' + response['Content-Transfer-Encoding'] = 'binary' + return response @login_required def add_new_profile (request): diff --git a/FusionIIIT/applications/academic_procedures/migrations/0002_initialregistration_registration_type.py b/FusionIIIT/applications/academic_procedures/migrations/0002_initialregistration_registration_type.py new file mode 100644 index 000000000..2bbd7e0c5 --- /dev/null +++ b/FusionIIIT/applications/academic_procedures/migrations/0002_initialregistration_registration_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2024-11-16 23:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('academic_procedures', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='initialregistration', + name='registration_type', + field=models.CharField(choices=[('Audit', 'Audit'), ('Improvement', 'Improvement'), ('Backlog', 'Backlog'), ('Regular', 'Regular')], default='Regular', max_length=20), + ), + ] diff --git a/FusionIIIT/applications/academic_procedures/migrations/0003_finalregistration_registration_type.py b/FusionIIIT/applications/academic_procedures/migrations/0003_finalregistration_registration_type.py new file mode 100644 index 000000000..376c82446 --- /dev/null +++ b/FusionIIIT/applications/academic_procedures/migrations/0003_finalregistration_registration_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2024-11-17 00:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('academic_procedures', '0002_initialregistration_registration_type'), + ] + + operations = [ + migrations.AddField( + model_name='finalregistration', + name='registration_type', + field=models.CharField(choices=[('Audit', 'Audit'), ('Improvement', 'Improvement'), ('Backlog', 'Backlog'), ('Regular', 'Regular')], default='Regular', max_length=20), + ), + ] diff --git a/FusionIIIT/applications/academic_procedures/models.py b/FusionIIIT/applications/academic_procedures/models.py index 2015a1ee8..6f33e293f 100644 --- a/FusionIIIT/applications/academic_procedures/models.py +++ b/FusionIIIT/applications/academic_procedures/models.py @@ -592,6 +592,7 @@ class InitialRegistration(models.Model): course_slot_id(programme_curriculum.CourseSlot) - details about under which course slot the course is offered(Optional/Core other details) timestamp - the time this entry was generated priority - priority of the selected course from the list of courses for the corresponding course_slot_it + registration_type - Type of registration for the course @@ -602,11 +603,21 @@ class InitialRegistration(models.Model): course_slot_id = models.ForeignKey(CourseSlot, null=True, blank=True,on_delete=models.SET_NULL) timestamp = models.DateTimeField(default=timezone.now) priority = models.IntegerField(blank=True,null=True) + REGISTRATION_TYPE_CHOICES = [ + ('Audit', 'Audit'), + ('Improvement', 'Improvement'), + ('Backlog', 'Backlog'), + ('Regular', 'Regular'), + ] + registration_type = models.CharField( + max_length=20, + choices=REGISTRATION_TYPE_CHOICES, + default='Regular', + ) class Meta: db_table = 'InitialRegistration' - - + class FinalRegistration(models.Model): ''' Current Purpose : stores information regarding the process of final(complete) registration of a student for a course @@ -627,6 +638,17 @@ class FinalRegistration(models.Model): student_id = models.ForeignKey(Student, on_delete=models.CASCADE) verified = models.BooleanField(default=False) course_slot_id = models.ForeignKey(CourseSlot, null=True, blank=True,on_delete=models.SET_NULL) + REGISTRATION_TYPE_CHOICES = [ + ('Audit', 'Audit'), + ('Improvement', 'Improvement'), + ('Backlog', 'Backlog'), + ('Regular', 'Regular'), + ] + registration_type = models.CharField( + max_length=20, + choices=REGISTRATION_TYPE_CHOICES, + default='Regular', + ) class Meta: db_table = 'FinalRegistration' @@ -690,7 +712,6 @@ def __str__(self): class Meta: db_table = 'course_registration' - class backlog_course(models.Model): ''' Current Purpose : stores information regarding the backlog courses of a student (purpose is unclear and is open to interpretations) diff --git a/FusionIIIT/applications/academic_procedures/views.py b/FusionIIIT/applications/academic_procedures/views.py index 5b2ea0f20..6e3b9f4f8 100644 --- a/FusionIIIT/applications/academic_procedures/views.py +++ b/FusionIIIT/applications/academic_procedures/views.py @@ -159,15 +159,69 @@ def academic_procedures_faculty(request): approved_assistantship_request_list = ta_approved_assistantship_request_list | thesis_approved_assistantship_request_list mtechseminar_request_list = MTechGraduateSeminarReport.objects.all().filter(Overall_grade = '') phdprogress_request_list = PhDProgressExamination.objects.all().filter(Overall_grade = '') - courses_list = list(CourseInstructor.objects.select_related('course_id', 'batch_id', 'batch_id__discipline').filter(instructor_id__id=fac_id.id).only('course_id__code', 'course_id__name', 'batch_id')) - - assigned_courses = CourseInstructor.objects.select_related('course_id', 'batch_id', 'batch_id__discipline').filter( - instructor_id__id=fac_id.id, # Filter by faculty ID - batch_id__running_batch=True, # Filter by currently running batches - course_id__working_course=True # Filter by currently active courses - ).only('course_id__code', 'course_id__name', 'batch_id') - assigned_courses = list(assigned_courses) + + # courses_list = list(CourseInstructor.objects.select_related('course_id', 'batch_id', 'batch_id__discipline').filter(instructor_id__id=fac_id.id).only('course_id__code', 'course_id__name', 'batch_id')) + # print(fac_id.id) + courses_list = list( + CourseInstructor.objects.filter(instructor_id=fac_id.id) + .values('course_id_id','course_id__code','course_id__version','course_id__name', 'year', 'semester_no') + ) + for course in courses_list: + # Calculate the batch as an integer + course['batch'] = int(course['year'] - (course['semester_no'] // 2)) + + # print(courses_list) + # # courses_list = list( + # CourseInstructor.objects.select_related('course_id', 'instructor_id') + # .filter(instructor_id=fac_id) + # # .only('course_id__code', 'course_id__name', 'course_id') + # ) + + + # assigned_courses = CourseInstructor.objects.select_related('course_id', 'batch_id', 'batch_id__discipline').filter( + # instructor_id__id=fac_id.id, # Filter by faculty ID + # batch_id__running_batch=True, # Filter by currently running batches + # course_id__working_course=True # Filter by currently active courses + # ).only('course_id__code', 'course_id__name', 'batch_id') + + + course_years = ( + CourseInstructor.objects + .select_related('course_id', 'instructor_id') + .filter( + instructor_id=fac_id.id, + course_id__working_course=True + ) + ) + + assigned_courses = [] + excluded_years = set() + + for course_instructor in course_years: + # Calculate target year based on course year and semester + target_year = course_instructor.year - course_instructor.semester_no // 2 + + # Check if all batches for this target year are inactive + batches_for_year = Batch.objects.filter(year=target_year) + if batches_for_year.exists() and not batches_for_year.filter(running_batch=True).exists(): + excluded_years.add(course_instructor.year) + + # Second pass: filter courses based on excluded years + filtered_courses = ( + CourseInstructor.objects + .select_related('course_id', 'instructor_id') + .filter( + instructor_id=fac_id.id, + course_id__working_course=True + ) + .exclude(year__in=excluded_years) + ) + # Ensure unique results in the final list + assigned_courses = list(filtered_courses) + # assigned_courses = list(assigned_courses) + # assigned_courses = [] + # courses_list = [] # print('------------------------------------------------------------------------------------------------------------------' , list(assigned_courses)) r = range(4) return render( @@ -393,7 +447,7 @@ def academic_procedures_student(request): final_registered_courses = FinalRegistration.objects.all().filter(student_id = user_details.id,semester_id = next_sem_id) final_registered_course_show=[] for final_registered_course in final_registered_courses: - final_registered_course_show.append({"course_code":final_registered_course.course_id.code,"course_name":final_registered_course.course_id.name,"course_credit":final_registered_course.course_id.credit}) + final_registered_course_show.append({"course_code":final_registered_course.course_id.code,"course_name":final_registered_course.course_id.name,"course_credit":final_registered_course.course_id.credit, "registration_type": final_registered_course.registration_type}) add_courses_options = get_add_course_options(current_sem_branch_course, currently_registered_course, batch.year) drop_courses_options = get_drop_course_options(currently_registered_course) replace_courses_options = get_replace_course_options(currently_registered_course, batch.year) @@ -1454,6 +1508,7 @@ def auto_pre_registration(request): existing_entries = set() for course_slot in course_slots : course_priorities = request.POST.getlist("course_priority-"+course_slot) + registration_type = request.POST.get('registration_type-'+course_slot, 'Regular') if(course_priorities[0] == 'NULL'): continue course_slot_id_for_model = CourseSlot.objects.get(id = int(course_slot)) @@ -1472,16 +1527,17 @@ def auto_pre_registration(request): semester_id = sem_id, student_id = current_user, course_slot_id = course_slot_id_for_model, - priority = priority_of_current_course + priority = priority_of_current_course, + registration_type = registration_type ) - f =FinalRegistration(student_id=current_user ,course_slot_id=course_slot_id_for_model , course_id=course_id_for_model ,semester_id=sem_id) - final_reg_curr.append(f) + # f =FinalRegistration(student_id=current_user ,course_slot_id=course_slot_id_for_model , course_id=course_id_for_model ,semester_id=sem_id) + # final_reg_curr.append(f) reg_curr.append(p) existing_entries.add(current_combination) try: InitialRegistration.objects.bulk_create(reg_curr) - FinalRegistration.objects.bulk_create(final_reg_curr) + # FinalRegistration.objects.bulk_create(final_reg_curr) registration_check = StudentRegistrationChecks( student_id = current_user, pre_registration_flag = True, diff --git a/FusionIIIT/applications/central_mess/migrations/0002_auto_20241012_1459.py b/FusionIIIT/applications/central_mess/migrations/0002_auto_20241012_1459.py new file mode 100644 index 000000000..fd5b1d205 --- /dev/null +++ b/FusionIIIT/applications/central_mess/migrations/0002_auto_20241012_1459.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.5 on 2024-10-12 14:59 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('central_mess', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='payments', + name='payment_date', + field=models.DateField(default=datetime.date.today), + ), + migrations.AlterUniqueTogether( + name='payments', + unique_together=set(), + ), + ] diff --git a/FusionIIIT/applications/central_mess/migrations/0002_auto_20241020_1126.py b/FusionIIIT/applications/central_mess/migrations/0002_auto_20241020_1126.py new file mode 100644 index 000000000..fa5bb79af --- /dev/null +++ b/FusionIIIT/applications/central_mess/migrations/0002_auto_20241020_1126.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.5 on 2024-10-20 11:26 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('central_mess', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='payments', + name='payment_date', + field=models.DateField(default=datetime.date.today), + ), + migrations.AlterUniqueTogether( + name='payments', + unique_together=set(), + ), + ] diff --git a/FusionIIIT/applications/central_mess/models.py b/FusionIIIT/applications/central_mess/models.py index ae96e8f82..3e6030952 100644 --- a/FusionIIIT/applications/central_mess/models.py +++ b/FusionIIIT/applications/central_mess/models.py @@ -154,7 +154,7 @@ class Payments(models.Model): amount_paid = models.IntegerField(default=0) payment_month = models.CharField(max_length=20, default=current_month) payment_year = models.IntegerField(default = current_year) - payment_date = models.DateField(default= datetime.date.today()) + payment_date = models.DateField(default= datetime.date.today) # class Meta: # unique_together = (('student_id', 'payment_date')) diff --git a/FusionIIIT/applications/examination/urls.py b/FusionIIIT/applications/examination/urls.py index 83789d49e..0f1254858 100644 --- a/FusionIIIT/applications/examination/urls.py +++ b/FusionIIIT/applications/examination/urls.py @@ -30,7 +30,7 @@ path('download_excel/', DownloadExcelView.as_view(), name='download_excel'),#old #new - url(r'submitGrades/', views.submitGrades, name='submitGrades'),#new + url(r'submitGrades/', views.submitGrades.as_view(), name='submitGrades'),#new url(r'submitEntergrades/', views.submitEntergrades, name='submitEntergrades'),#new path('submitEntergradesStoring/', views.submitEntergradesStoring.as_view(),#new name='submitEntergradesStoring'), @@ -52,6 +52,18 @@ name='generate_transcript_form'),#new # Announcement url(r'announcement/', views.announcement, name='announcement'),#new - + path('upload_grades/',views.upload_grades,name='upload_grades'), + path('message/',views.show_message,name='message'), + path('submitGradesProf/',views.submitGradesProf,name='submitGradesProf'), + path('download_template/',views.download_template,name='download_template'), + path('verifyGradesDean/',views.verifyGradesDean,name='verifyGradesDean'), + path('updateEntergradesDean/',views.updateEntergradesDean,name='updateEnterGradesDean'), + path('upload_grades_prof/',views.upload_grades_prof,name='upload_grades_prof'), + path('validateDean/',views.validateDean,name='validateDean'), + path('validateDeanSubmit/',views.validateDeanSubmit,name='validateDeanSubmit'), + path('downloadGrades/',views.downloadGrades,name='downloadGrades'), + path('generate_pdf/',views.generate_pdf,name='generate_pdf'), + path('generate-result/',views.generate_result,name='generate_pdf'), +# path('get_courses/',views.get_courses,name='get_courses'), ] diff --git a/FusionIIIT/applications/examination/views.py b/FusionIIIT/applications/examination/views.py index e65f34b3e..903e55eba 100644 --- a/FusionIIIT/applications/examination/views.py +++ b/FusionIIIT/applications/examination/views.py @@ -1,8 +1,14 @@ from notifications.signals import notify from django.views import View from django.views.generic import View +import traceback from django.http import HttpResponse import csv +import json +from openpyxl import Workbook +from openpyxl.styles import Alignment, Font +from openpyxl.utils import get_column_letter +from io import BytesIO,StringIO from django.db.models import IntegerField from django.db.models.functions import Cast from django.db.models.query_utils import Q @@ -20,35 +26,51 @@ from django.contrib.auth.models import User from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from applications.academic_information.models import Spi, Student, Curriculum -from applications.globals.models import (Designation, ExtraInfo, - HoldsDesignation, Faculty) -from applications.eis.models import (faculty_about, emp_research_projects) +from applications.globals.models import ( + Designation, + ExtraInfo, + HoldsDesignation, + Faculty, +) +from applications.eis.models import faculty_about, emp_research_projects from applications.academic_information.models import Course -from applications.academic_procedures.models import course_registration, Register +from applications.academic_procedures.models import course_registration, Register,Semester from applications.programme_curriculum.filters import CourseFilter from notification.views import examination_notif from applications.department.models import SpecialRequest, Announcements -from applications.globals.models import (DepartmentInfo, Designation, - ExtraInfo, Faculty, HoldsDesignation) +from applications.globals.models import ( + DepartmentInfo, + Designation, + ExtraInfo, + Faculty, + HoldsDesignation, +) from jsonschema import validate from jsonschema.exceptions import ValidationError from django.shortcuts import render, redirect, HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status -from .models import hidden_grades +from .models import hidden_grades, grade from .forms import StudentGradeForm from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import hidden_grades, authentication from rest_framework.permissions import AllowAny -from applications.online_cms.models import (Student_grades) - -from applications.programme_curriculum.models import Course as Courses, CourseInstructor - - -@login_required(login_url='/accounts/login') +from applications.online_cms.models import Student_grades +from django.http import JsonResponse +import csv +from applications.programme_curriculum.models import Course as Courses, CourseInstructor,Discipline,Batch, CourseSlot +from django.urls import reverse +from reportlab.pdfgen import canvas +from reportlab.lib.pagesizes import letter +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer +from reportlab.lib import colors +from reportlab.lib.colors import HexColor +from reportlab.lib.units import inch +@login_required(login_url="/accounts/login") def exam(request): """ This function is used to Differenciate acadadmin and all other user. @@ -59,15 +81,21 @@ def exam(request): @variables: user_details - Gets the information about the logged in user. des - Gets the designation about the looged in user. - # """ + #""" user_details = ExtraInfo.objects.get(user=request.user) - des = HoldsDesignation.objects.all().filter(user=request.user).first() - if str(des.designation) == "Associate Professor" or str(des.designation) == "Professor" or str(des.designation) == "Assistant Professor": - return HttpResponseRedirect('/examination/updateGrades/') + des = request.session.get("currentDesignationSelected") + if ( + str(des) == "Associate Professor" + or str(des) == "Professor" + or str(des) == "Assistant Professor" + ): + return HttpResponseRedirect("/examination/submitGradesProf/") elif request.session.get("currentDesignationSelected") == "acadadmin": - return HttpResponseRedirect('/examination/updateGrades/') + return HttpResponseRedirect("/examination/updateGrades/") + elif request.session.get("currentDesignationSelected") == "Dean Academic": + return HttpResponseRedirect("/examination/verifyGradesDean/") - return HttpResponseRedirect('/dashboard/') + return HttpResponseRedirect("/dashboard/") @login_required(login_url='/accounts/login') @@ -134,7 +162,7 @@ def browse_announcements(): me_ann = Announcements.objects.filter(department="ME") sm_ann = Announcements.objects.filter(department="SM") all_ann = Announcements.objects.filter(department="ALL") - + print(cse_ann) context = { "cse": cse_ann, "ece": ece_ann, @@ -302,7 +330,7 @@ def announcement(request): department = request.POST.get('department') ann_date = date.today() - obj1, created = Announcements.objects.get_or_create( + obj1 = Announcements.objects.get_or_create( maker_id=user_info, batch=batch, programme=programme, @@ -314,7 +342,8 @@ def announcement(request): recipients = User.objects.all() # Modify this query as per your requirements examination_notif(sender=usrnm, recipient=recipients, type=message) - + return render(request,'department/browse_announcements_staff.html') + print(user_info.user_type) context = browse_announcements() return render(request, 'examination/announcement_req.html', { "user_designation": user_info.user_type, @@ -486,29 +515,29 @@ def generate_transcript(request): # course_detail = Curriculum.objects.get( # course_id=course.course_id, batch=grade.batch) course_instance = Courses.objects.get(id=course.course_id_id) - check_authentication_object = authentication.objects.filter( - course_id=course_instance, course_year=grade.year) - all_authenticators_true = True - - if check_authentication_object: - # Iterate over each authentication object - for auth_obj in check_authentication_object: - # Check if all authenticators are true - if not (auth_obj.authenticator_1 and auth_obj.authenticator_2 and auth_obj.authenticator_3): - all_authenticators_true = False - break # No need to check further if any authenticator is False - else: + # check_authentication_object = authentication.objects.filter( + # course_id=course_instance, course_year=grade.year) + # all_authenticators_true = True + + # if check_authentication_object: + # # Iterate over each authentication object + # for auth_obj in check_authentication_object: + # # Check if all authenticators are true + # if not (auth_obj.authenticator_1 and auth_obj.authenticator_2 and auth_obj.authenticator_3): + # all_authenticators_true = False + # break # No need to check further if any authenticator is False + # else: # Create authentication object if it doesn't exist - authentication_object = authentication.objects.create( - course_id=course_instance, course_year=grade.year) + # authentication_object = authentication.objects.create( + # course_id=course_instance, course_year=grade.year) # Get all registrations for the course and year - registrations = authentication.objects.filter( - course_id=course_instance, course_year=grade.year) - all_authenticators_true = False + # registrations = authentication.objects.filter( + # course_id=course_instance, course_year=grade.year) + # all_authenticators_true = False course_grades[course_instance] = { 'grade': grade, - 'all_authenticators_true': all_authenticators_true + # 'all_authenticators_true': all_authenticators_true } # Store the grade except Student_grades.DoesNotExist: # Grade not available @@ -560,46 +589,54 @@ def generate_transcript_form(request): return render(request, 'examination/generate_transcript_form.html', context) -@login_required(login_url='/accounts/login') +@login_required(login_url="/accounts/login") def updateGrades(request): - unique_course_ids = Student_grades.objects.values( - 'course_id').distinct() + unique_course_ids = Student_grades.objects.values("course_id").distinct() # Cast the course IDs to integers unique_course_ids = unique_course_ids.annotate( - course_id_int=Cast('course_id', IntegerField())) + course_id_int=Cast("course_id", IntegerField()) + ) # Retrieve course names and course codes based on unique course IDs - print(unique_course_ids) + # print(unique_course_ids) courses_info = Courses.objects.filter( - id__in=unique_course_ids.values_list('course_id_int', flat=True)) - - unique_batch_ids = Student_grades.objects.values( - 'batch').distinct() + id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + unique_year_ids = Student_grades.objects.values('year').distinct() + # print(unique_year_ids) context = { - 'courses_info': courses_info, - 'unique_batch_ids': unique_batch_ids, + "courses_info": courses_info, + "unique_year_ids": unique_year_ids, } - return render(request, '../templates/examination/submitGrade.html', context) + return render(request, "../templates/examination/submitGrade.html", context) def updateEntergrades(request): - course_id = request.GET.get('course') - semester_id = request.GET.get('semester') - batch = request.GET.get('batch') - + course_id = request.GET.get("course") + + year = request.GET.get("year") + # print(course_id,semester_id ,year) course_present = Student_grades.objects.filter( - course_id=course_id, semester=semester_id, batch=batch) + course_id=course_id, year=year + ) - context = { - 'registrations': course_present - } + if not course_present: + context = {"message": "THIS COURSE IS NOT SUBMITTED BY THE INSTRUCTOR"} + return render(request, "../templates/examination/message.html", context) + + verification = course_present.first().verified + print(verification) + if verification: + context = {"message": "THIS COURSE IS VERIFIED"} + return render(request, "../templates/examination/message.html", context) - return render(request, '../templates/examination/updateEntergrades.html', context) + context = {"registrations": course_present} + return render(request, "../templates/examination/updateEntergrades.html", context) class moderate_student_grades(APIView): permission_classes = [AllowAny] @@ -609,7 +646,8 @@ def post(self, request): semester_ids = request.POST.getlist('semester_ids[]') course_ids = request.POST.getlist('course_ids[]') grades = request.POST.getlist('grades[]') - + allow_resubmission = request.POST.get('allow_resubmission', 'NO') + if len(student_ids) != len(semester_ids) != len(course_ids) != len(grades): return Response({'error': 'Invalid grade data provided'}, status=status.HTTP_400_BAD_REQUEST) @@ -619,6 +657,9 @@ def post(self, request): grade_of_student = Student_grades.objects.get( course_id=course_id, roll_no=student_id, semester=semester_id) grade_of_student.grade = grade + grade_of_student.verified = True + if allow_resubmission == 'YES': + grade_of_student.reSubmit = True grade_of_student.save() except Student_grades.DoesNotExist: # If the grade doesn't exist, create a new one @@ -634,119 +675,1011 @@ def post(self, request): writer.writerow(['Student ID', 'Semester ID', 'Course ID', 'Grade']) for student_id, semester_id, course_id, grade in zip(student_ids, semester_ids, course_ids, grades): writer.writerow([student_id, semester_id, course_id, grade]) - + print("HELLO") return response return render(request, '../templates/examination/grades_updated.html', {}) -@login_required(login_url='/accounts/login') -def submitGrades(request): - - unique_course_ids = course_registration.objects.values( - 'course_id').distinct() - working_years = course_registration.objects.values( - 'working_year').distinct() - - - - # Cast the course IDs to integers - unique_course_ids = unique_course_ids.annotate( - course_id_int=Cast('course_id', IntegerField())) - - # Retrieve course names and course codes based on unique course IDs - - print(unique_course_ids) - courses_info = Courses.objects.filter( - id__in=unique_course_ids.values_list('course_id_int', flat=True)) - - context = { - 'courses_info': courses_info, - 'working_years': working_years - } - - print(working_years) - - return render(request, '../templates/examination/gradeSubmission.html', context) +class submitGrades(APIView): + login_url = "/accounts/login" + + def get(self, request): + academic_year = request.GET.get('academic_year') + + if academic_year: + if academic_year is None or not academic_year.isdigit(): + return JsonResponse({}) + # Filter course registration based on the academic year and get unique course IDs + unique_course_ids = course_registration.objects.filter( + working_year=academic_year + ).values("course_id").distinct() + + # Cast the course IDs to integers + unique_course_ids = unique_course_ids.annotate( + course_id_int=Cast("course_id", IntegerField()) + ) + + # Retrieve course information based on the unique course IDs + courses_info = Courses.objects.filter( + id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + + # Return the course information as JSON response + return JsonResponse({"courses": list(courses_info.values())}) + + # If no academic year is provided, return the working years for the dropdown + working_years = course_registration.objects.values("working_year").distinct() + + context = {"working_years": working_years} + + return render(request, "../templates/examination/gradeSubmission.html", context) def submitEntergrades(request): - course_id = request.GET.get('course') - year = request.GET.get('year') + + course_id = request.GET.get("course") + year = request.GET.get("year") if year is None or not year.isdigit(): message = "YEAR SHOULD NOT BE NONE" - context = { - 'message': message - } + context = {"message": message} - return render(request, '../templates/examination/message.html', context) + return render(request, "../templates/examination/message.html", context) return HttpResponse("Invalid year parameter") # Handle invalid year parameter # You can return an error response or redirect the user to an error page courses_info = Courses.objects.get(id=course_id) - courses = Student_grades.objects.filter( - course_id=courses_info.id, year=year) + courses = Student_grades.objects.filter(course_id=courses_info.id, year=year) if courses: message = "THIS Course was Already Submitted" - context = { - 'message': message - } + context = {"message": message} - return render(request, '../templates/examination/message.html', context) + return render(request, "../templates/examination/message.html", context) students = course_registration.objects.filter( - course_id_id=course_id, working_year=year) + course_id_id=course_id, working_year=year + ) # print(students) - context = { - 'registrations': students, - 'curr_id': course_id, - 'year': year - } + context = {"registrations": students, "curr_id": course_id, "year": year} - return render(request, '../templates/examination/gradeSubmissionForm.html', context) + return render(request, "../templates/examination/gradeSubmissionForm.html", context) class submitEntergradesStoring(APIView): permission_classes = [AllowAny] def post(self, request): - student_ids = request.POST.getlist('student_ids[]') - batch_ids = request.POST.getlist('batch_ids[]') - course_ids = request.POST.getlist('course_ids[]') - semester_ids = request.POST.getlist('semester_ids[]') - year_ids = request.POST.getlist('year_ids[]') - marks = request.POST.getlist('marks[]') - grades = request.POST.getlist('grades[]') - - if len(student_ids) != len(batch_ids) != len(course_ids) != len(semester_ids) != len(year_ids) != len(marks) != len(grades): - return Response({'error': 'Invalid grade data provided'}, status=status.HTTP_400_BAD_REQUEST) + student_ids = request.POST.getlist("student_ids[]") + batch_ids = request.POST.getlist("batch_ids[]") + course_ids = request.POST.getlist("course_ids[]") + semester_ids = request.POST.getlist("semester_ids[]") + year_ids = request.POST.getlist("year_ids[]") + marks = request.POST.getlist("marks[]") + Student_grades = request.POST.getlist("Student_grades[]") + + if ( + len(student_ids) + != len(batch_ids) + != len(course_ids) + != len(semester_ids) + != len(year_ids) + != len(marks) + != len(Student_grades) + ): + return Response( + {"error": "Invalid Student_grades data provided"}, + status=status.HTTP_400_BAD_REQUEST, + ) - for student_id, batch_id, course_id, semester_id, year_id, mark, grade in zip(student_ids, batch_ids, course_ids, semester_ids, year_ids, marks, grades): + for ( + student_id, + batch_id, + course_id, + semester_id, + year_id, + mark, + Student_grades, + ) in zip( + student_ids, + batch_ids, + course_ids, + semester_ids, + year_ids, + marks, + Student_grades, + ): # Create an instance of hidden_grades model and save the data try: grade_of_student = Student_grades.objects.get( - course_id=course_id, roll_no=student_id, semester=semester_id) + course_id=course_id, roll_no=student_id, semester=semester_id + ) except Student_grades.DoesNotExist: - # If the grade doesn't exist, create a new one + # If the Student_grades doesn't exist, create a new one course_instance = Courses.objects.get(id=course_id) student_grade = Student_grades.objects.create( - course_id=course_instance, roll_no=student_id, semester=semester_id, grade=grade, batch=batch_id, year=year_id, total_marks=mark) + course_id=course_instance, + roll_no=student_id, + semester=semester_id, + Student_grades=Student_grades, + batch=batch_id, + year=year_id, + total_marks=mark, + ) student_grade.save() - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="grades.csv"' + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = 'attachment; filename="Student_grades.csv"' # Write data to CSV writer = csv.writer(response) - writer.writerow(['student_id', 'batch_ids', 'course_id', - 'semester_id', 'year_ids', 'marks', 'grade']) - for student_id, batch_id, course_id, semester_id, year_id, mark, grade in zip(student_ids, batch_ids, course_ids, semester_ids, year_ids, marks, grades): - writer.writerow([student_id, batch_id, course_id, - semester_id, year_id, mark, grade]) + writer.writerow( + [ + "student_id", + "batch_ids", + "course_id", + "semester_id", + "year_ids", + "marks", + "Student_grades", + ] + ) + for ( + student_id, + batch_id, + course_id, + semester_id, + year_id, + mark, + Student_grades, + ) in zip( + student_ids, + batch_ids, + course_ids, + semester_ids, + year_ids, + marks, + Student_grades, + ): + writer.writerow( + [ + student_id, + batch_id, + course_id, + semester_id, + year_id, + mark, + Student_grades, + ] + ) return response - return render(request, '../templates/examination/grades_updated.html', {}) + return render(request, "../templates/examination/grades_updated.html", {}) + + +def upload_grades(request): + if request.method == "POST" and request.FILES.get("csv_file"): + csv_file = request.FILES["csv_file"] + + if not csv_file.name.endswith(".csv"): + return JsonResponse( + {"error": "Invalid file format. Please upload a CSV file."}, status=400 + ) + + course_id = request.POST.get("course_id") + academic_year = request.POST.get("academic_year") + # semester = request.POST.get('semester') + + if academic_year == "None" or not academic_year.isdigit(): + return JsonResponse( + {"error": "Academic year must be a valid number."}, status=400 + ) + + if not course_id or not academic_year: + return JsonResponse( + {"error": "Course ID and Academic Year are required."}, status=400 + ) + + courses_info = Courses.objects.get(id=course_id) + + courses = Student_grades.objects.filter( + course_id=courses_info.id, year=academic_year + ) + students = course_registration.objects.filter( + course_id_id=course_id, working_year=academic_year + ) + + if not students: + message = "NO STUDENTS REGISTERED IN THIS COURSE THIS SEMESTER" + redirect_url = reverse("examination:message") + f"?message={message}" + return JsonResponse( + {"error": message, "redirect_url": redirect_url}, status=400 + ) + + if courses: + message = "THIS Course was Already Submitted" + redirect_url = reverse("examination:message") + f"?message={message}" + return JsonResponse( + {"error": message, "redirect_url": redirect_url}, status=400 + ) + + try: + # Parse the CSV file + decoded_file = csv_file.read().decode("utf-8").splitlines() + reader = csv.DictReader(decoded_file) + + required_columns = ["roll_no", "grade", "remarks"] + if not all(column in reader.fieldnames for column in required_columns): + return JsonResponse( + { + "error": "CSV file must contain the following columns: roll_no, grade, remarks." + }, + status=400, + ) + + for row in reader: + roll_no = row["roll_no"] + grade = row["grade"] + remarks = row["remarks"] + batch_prefix = roll_no[:2] + batch = int(f"20{batch_prefix}") + semester=Student.objects.get(id_id=roll_no).curr_semester_no + + Student_grades.objects.create( + roll_no=roll_no, + grade=grade, + remarks=remarks, + course_id_id=course_id, + year=academic_year, + semester=semester, + batch=batch, + ) + des = request.session.get("currentDesignationSelected") + if ( + str(des) == "Associate Professor" + or str(des) == "Professor" + or str(des) == "Assistant Professor" + ): + return JsonResponse( + { + "message": "Grades uploaded successfully.", + "redirect_url": "/examination/submitGradesProf", + } + ) + return JsonResponse( + { + "message": "Grades uploaded successfully.", + "redirect_url": "/examination/submitGrades", + } + ) + + except Courses.DoesNotExist: + return JsonResponse({"error": "Invalid course ID."}, status=400) + + except Exception as e: + return JsonResponse({"error": f"An error occurred: {e}"}, status=500) + + return JsonResponse( + {"error": "Invalid request. Please upload a CSV file."}, status=400 + ) + + +def show_message(request): + message = request.GET.get("message", "Default message if none provided.") + des = request.session.get("currentDesignationSelected") + if ( + str(des) == "Associate Professor" + or str(des) == "Professor" + or str(des) == "Assistant Professor" + ): + return render(request, "examination/messageProf.html", {"message": message}) + return render(request, "examination/message.html", {"message": message}) + + +@login_required(login_url="/accounts/login") +def submitGradesProf(request): + # print(request.user,1) + unique_course_ids = ( + CourseInstructor.objects.filter(instructor_id_id=request.user.username) + .values("course_id_id") + .distinct() + ) + # unique_course_ids = course_registration.objects.values( + # 'course_id').distinct() + working_years = course_registration.objects.values("working_year").distinct() + course_ids_final=course_registration.objects.filter() + # Cast the course IDs to integers + unique_course_ids = unique_course_ids.annotate( + course_id_int=Cast("course_id", IntegerField()) + ) + + # Retrieve course names and course codes based on unique course IDs + + # print(unique_course_ids) + courses_info = Courses.objects.filter( + id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + + context = {"courses_info": courses_info, "working_years": working_years} + + print(working_years) + + return render(request, "../templates/examination/submitGradesProf.html", context) + + +def download_template(request): + course = request.GET.get('course') + year = request.GET.get('year') + + if not course or not year: + return JsonResponse({'error': 'Course and year are required'}, status=400) + + try: + + course_info = course_registration.objects.filter(course_id_id=course, working_year=year) + + + if not course_info.exists(): + return JsonResponse({'error': 'No registration data found for the provided course and year'}, status=404) + + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="template.csv"' + writer = csv.writer(response) + + writer.writerow(["roll_no", "grade", "remarks"]) + + for entry in course_info: + student = entry.student_id + writer.writerow([student.id_id, "", ""]) + + return response + + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + + + +def verifyGradesDean(request): + unique_course_ids = Student_grades.objects.filter(verified=True).values("course_id").distinct() + + # Cast the course IDs to integers + unique_course_ids = unique_course_ids.annotate( + course_id_int=Cast("course_id", IntegerField()) + ) + + # Retrieve course names and course codes based on unique course IDs + + # print(unique_course_ids) + courses_info = Courses.objects.filter( + id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + + unique_batch_ids = Student_grades.objects.values("batch").distinct() + + context = { + "courses_info": courses_info, + "unique_batch_ids": unique_batch_ids, + } + + return render(request, "../templates/examination/submitGradeDean.html", context) + + +def updateEntergradesDean(request): + course_id = request.GET.get("course") + + batch = request.GET.get("batch") + course_present = Student_grades.objects.filter( + course_id=course_id, batch=batch + ) + + if not course_present: + context = {"message": "THIS COURSE IS NOT SUBMITTED BY THE INSTRUCTOR"} + return render(request, "../templates/examination/messageDean.html", context) + + context = {"registrations": course_present} + + return render(request, "../templates/examination/updateEntergradesDean.html", context) + + + +def upload_grades_prof(request): + if request.method == "POST" and request.FILES.get("csv_file"): + csv_file = request.FILES["csv_file"] + + if not csv_file.name.endswith(".csv"): + return JsonResponse( + {"error": "Invalid file format. Please upload a CSV file."}, status=400 + ) + + course_id = request.POST.get("course_id") + academic_year = request.POST.get("academic_year") + # semester = request.POST.get('semester') + + if academic_year == "None" or not academic_year.isdigit(): + return JsonResponse( + {"error": "Academic year must be a valid number."}, status=400 + ) + + if not course_id or not academic_year: + return JsonResponse( + {"error": "Course ID and Academic Year are required."}, status=400 + ) + + courses_info = Courses.objects.get(id=course_id) + + courses = Student_grades.objects.filter( + course_id=courses_info.id, year=academic_year + ) + students = course_registration.objects.filter( + course_id_id=course_id, working_year=academic_year + ) + + if not students: + message = "NO STUDENTS REGISTERED IN THIS COURSE THIS SEMESTER" + redirect_url = reverse("examination:message") + f"?message={message}" + return JsonResponse( + {"error": message, "redirect_url": redirect_url}, status=400 + ) + + if courses and not courses.first().reSubmit: + + message = "THIS Course was Already Submitted" + redirect_url = reverse("examination:message") + f"?message={message}" + return JsonResponse( + {"error": message, "redirect_url": redirect_url}, status=400 + ) + + + + try: + # Parse the CSV file + decoded_file = csv_file.read().decode("utf-8").splitlines() + reader = csv.DictReader(decoded_file) + + required_columns = ["roll_no", "grade", "remarks"] + if not all(column in reader.fieldnames for column in required_columns): + return JsonResponse( + { + "error": "CSV file must contain the following columns: roll_no, grade, remarks." + }, + status=400, + ) + + for row in reader: + roll_no = row["roll_no"] + grade = row["grade"] + remarks = row["remarks"] + batch_prefix = roll_no[:2] + batch = int(f"20{batch_prefix}") + semester=Student.objects.get(id_id=roll_no).curr_semester_no + reSubmit=False + Student_grades.objects.update_or_create( + roll_no=roll_no, + course_id_id=course_id, + year=academic_year, + semester=semester, + batch=batch, + # Fields that will be updated if a match is found + defaults={ + 'grade': grade, + 'remarks': remarks, + 'reSubmit': reSubmit, + } + ) + des = request.session.get("currentDesignationSelected") + if ( + str(des) == "Associate Professor" + or str(des) == "Professor" + or str(des) == "Assistant Professor" + ): + return JsonResponse( + { + "message": "Grades uploaded successfully.", + "redirect_url": "/examination/submitGradesProf", + } + ) + return JsonResponse( + { + "message": "Grades uploaded successfully.", + "redirect_url": "/examination/submitGrades", + } + ) + + except Courses.DoesNotExist: + return JsonResponse({"error": "Invalid course ID."}, status=400) + + except Exception as e: + return JsonResponse({"error": f"An error occurred: {e}"}, status=500) + + return JsonResponse( + {"error": "Invalid request. Please upload a CSV file."}, status=400 + ) + +def validateDean(request): + unique_course_ids = Student_grades.objects.filter(verified=True).values("course_id").distinct() + + # Cast the course IDs to integers + unique_course_ids = unique_course_ids.annotate( + course_id_int=Cast("course_id", IntegerField()) + ) + + # Retrieve course names and course codes based on unique course IDs + + # print(unique_course_ids) + courses_info = Courses.objects.filter( + id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + working_years = course_registration.objects.values("working_year").distinct() + + unique_batch_ids = Student_grades.objects.values("batch").distinct() + + context = {"courses_info": courses_info, "working_years": working_years} + + return render(request, "../templates/examination/validation.html", context) + + +def validateDeanSubmit(request): + if request.method == "POST" and request.FILES.get("csv_file"): + csv_file = request.FILES["csv_file"] + + if not csv_file.name.endswith(".csv"): + message = "Please Submit a csv file " + context = { + "message":message, + } + return render(request, "../templates/examination/messageDean.html", context) + + course_id = request.POST.get("course") + academic_year = request.POST.get("year") + # semester = request.POST.get('semester') + print(academic_year) + if academic_year is None or not academic_year.isdigit(): + message = "Academic Year must be a number" + context = { + "message":message, + } + return render(request, "../templates/examination/messageDean.html", context) + + if not course_id or not academic_year: + message = "Course and Academic year are required" + context = { + "message":message, + } + return render(request, "../templates/examination/messageDean.html", context) + + # courses_info = Courses.objects.get(id=course_id) + + # courses = Student_grades.objects.filter( + # course_id=courses_info.id, year=academic_year + # ) + students = course_registration.objects.filter( + course_id_id=course_id, working_year=academic_year + ) + + + + try: + # Parse the CSV file + decoded_file = csv_file.read().decode("utf-8").splitlines() + reader = csv.DictReader(decoded_file) + + required_columns = ["roll_no", "name", "grade", "remarks"] + if not all(column in reader.fieldnames for column in required_columns): + message = "CSV file must contain the following columns: roll_no, name, grade, remarks." + context = { + "message":message, + } + return render(request, "../templates/examination/messageDean.html", context) + + + mismatch=[] + for row in reader: + roll_no = row["roll_no"] + grade = row["grade"] + remarks = row["remarks"] + batch_prefix = roll_no[:2] + batch = int(f"20{batch_prefix}") + semester=Student.objects.get(id_id=roll_no).curr_semester_no + Student_grades.objects.filter( + roll_no=roll_no, + course_id_id=course_id, + year=academic_year, + batch=batch, + ) + student_grade = Student_grades.objects.get( + roll_no=roll_no, + course_id_id=course_id, + year=academic_year, + batch=batch + ) + if student_grade.grade != grade: + mismatch.append({ + "roll_no": roll_no, + "csv_grade": grade, + "db_grade": student_grade.grade, + "remarks": remarks, + "batch": batch, + "semester": semester, + "course_id": course_id, + }) + if not mismatch: + message = "There Are no Mismatches" + context = { + "message":message, + } + return render(request, "../templates/examination/messageDean.html", context) + context = { + "mismatch": mismatch, + } + return render(request, "../templates/examination/validationSubmit.html", context) + + except Exception as e: + + error_message = f"An error occurred while processing the file: {str(e)}" + context = { + "message": error_message, + } + return render(request, "../templates/examination/messageDean.html", context) + + +def downloadGrades(request): + academic_year = request.GET.get('academic_year') + + if academic_year: + if academic_year is None or not academic_year.isdigit(): + return JsonResponse({}) + # print(request.user,1) + unique_course_ids = ( + CourseInstructor.objects.filter(instructor_id_id=request.user.username) + .values("course_id_id") + .distinct() + ) + # unique_course_ids = course_registration.objects.values( + # 'course_id').distinct() + + # Cast the course IDs to integers + unique_course_ids = unique_course_ids.annotate( + course_id_int=Cast("course_id", IntegerField()) + ) + + # Retrieve course names and course codes based on unique course IDs + + # print(unique_course_ids) + courses_info = Student_grades.objects.filter( + year=academic_year, + course_id_id__in=unique_course_ids.values_list("course_id_int", flat=True) + ) + courses_details=Courses.objects.filter( + id__in=courses_info.values_list("course_id_id", flat=True) + ) + # print(courses_info.values(),'abcd') + return JsonResponse({"courses": list(courses_details.values())}) + + + working_years = course_registration.objects.values("working_year").distinct() + + context = {"working_years": working_years} + + return render(request, "../templates/examination/download_resultProf.html", context) + + +# def get_courses(request): +def generate_pdf(request): + course_id = request.POST.get('course_id') + academic_year = request.POST.get('academic_year') + course_info = get_object_or_404(Courses, id=course_id) + grades = Student_grades.objects.filter(course_id_id=course_id, year=academic_year).order_by("roll_no") + + # Calculate grade counts + all_grades = ["O", "A+", "A", "B+", "B", "C+", "C", "D+", "D", "F", "I", "S", "X"] + grade_counts = {grade: grades.filter(grade=grade).count() for grade in all_grades} + + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = f'attachment; filename="{course_info.code}_grades.pdf"' + + doc = SimpleDocTemplate(response, pagesize=letter) + elements = [] + styles = getSampleStyleSheet() + + # Custom Header Style + header_style = ParagraphStyle( + "HeaderStyle", + parent=styles["Heading1"], + fontName="Helvetica-Bold", + fontSize=16, + textColor=HexColor("#333333"), + spaceAfter=20, + alignment=1, # Center alignment + ) + subheader_style = ParagraphStyle( + "SubheaderStyle", + parent=styles["Normal"], + fontSize=12, + textColor=HexColor("#666666"), + spaceAfter=10, + ) + instructor = request.user.first_name + " " + request.user.last_name + + # Add Header + elements.append(Paragraph(f"Grade Sheet", header_style)) + field_label_style = ParagraphStyle( + "FieldLabelStyle", + parent=styles["Normal"], + fontSize=12, + textColor=colors.black, # Black text color for labels + spaceAfter=5, +) + field_value_style = ParagraphStyle( + "FieldValueStyle", + parent=styles["Normal"], + fontSize=12, + textColor=HexColor("#666666"), # Gray text color for values + spaceAfter=10, +) + +# Add fields with labels in black and values in gray + elements.append(Paragraph(f"Session: {academic_year}", field_label_style)) + elements.append(Paragraph(f"Semester: {grades.first().semester}", field_label_style)) + elements.append(Paragraph(f"Course Code: {course_info.code}", field_label_style)) + elements.append(Paragraph(f"Course Name: {course_info.name}", field_label_style)) + elements.append(Paragraph(f"Instructor: {instructor}", field_label_style)) + + # Table Data with Wider Column Widths + data = [["S.No.", "Roll Number", "Grade"]] + for i, grade in enumerate(grades, 1): + data.append([i, grade.roll_no, grade.grade]) + table = Table(data, colWidths=[80, 300, 100]) + + # Improved Table Style + table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), HexColor("#E0E0E0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.black), + ("ALIGN", (0, 0), (-1, 0), "CENTER"), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, 0), 14), + ("BOTTOMPADDING", (0, 0), (-1, 0), 10), + ("BACKGROUND", (0, 1), (-1, -1), HexColor("#F9F9F9")), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#F9F9F9"), colors.white]), + ("TEXTCOLOR", (0, 1), (-1, -1), colors.black), + ("FONTNAME", (0, 1), (-1, -1), "Helvetica"), + ("FONTSIZE", (0, 1), (-1, -1), 12), + ("GRID", (0, 0), (-1, -1), 0.5, colors.grey), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ] + ) + ) + elements.append(table) + elements.append(Spacer(1, 20)) + + # Add Grade Distribution with Row Splitting + elements.append(Paragraph(f"Grade Distribution:", header_style)) + + # First Grade Table + grade_data1 = [["O", "A+", "A", "B+", "B", "C+", "C", "D+"]] + grade_data1.append([grade_counts[grade] for grade in grade_data1[0]]) + grade_table1 = Table(grade_data1, colWidths=[60] * 8) + grade_table1.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), HexColor("#E0E0E0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.black), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, 0), 12), + ("BOTTOMPADDING", (0, 0), (-1, 0), 8), + ("GRID", (0, 0), (-1, -1), 0.5, colors.grey), + ("BACKGROUND", (0, 1), (-1, -1), colors.white), + ("TEXTCOLOR", (0, 1), (-1, -1), colors.black), + ] + ) + ) + elements.append(grade_table1) + elements.append(Spacer(1, 10)) + + # Second Grade Table + grade_data2 = [["D", "F", "I", "S", "X"]] + grade_data2.append([grade_counts[grade] for grade in grade_data2[0]]) + grade_table2 = Table(grade_data2, colWidths=[60] * 5) + grade_table2.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), HexColor("#E0E0E0")), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.black), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("FONTSIZE", (0, 0), (-1, 0), 12), + ("BOTTOMPADDING", (0, 0), (-1, 0), 8), + ("GRID", (0, 0), (-1, -1), 0.5, colors.grey), + ("BACKGROUND", (0, 1), (-1, -1), colors.white), + ("TEXTCOLOR", (0, 1), (-1, -1), colors.black), + ] + ) + ) + elements.append(grade_table2) + elements.append(Spacer(1, 20)) + + # Footer Signatures + def draw_signatures(canvas, doc): + canvas.saveState() + width, height = letter + canvas.drawString(inch, 0.75 * inch, "") + canvas.drawString(inch, 0.5 * inch, "Date") + canvas.drawString(width - 4 * inch, 0.75 * inch, "") + canvas.drawString(width - 4 * inch, 0.5 * inch, "Course Instructor's Signature") + canvas.restoreState() + + doc.build(elements, onLaterPages=draw_signatures, onFirstPage=draw_signatures) + return response + + + +def generate_result(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + semester = data.get('semester') + branch = data.get('specialization') + batch = data.get('batch') + + branch_info = Discipline.objects.filter(acronym=branch).first() + if not branch_info: + return JsonResponse({'error': 'Branch not found'}, status=404) + + curriculum_id = Batch.objects.filter( + year=batch, discipline_id=branch_info.id + ).values_list('curriculum_id', flat=True).first() + if not curriculum_id: + return JsonResponse({'error': 'Curriculum not found'}, status=404) + + semester_info = Semester.objects.filter( + curriculum_id=curriculum_id, semester_no=semester + ).first() + if not semester_info: + return JsonResponse({'error': 'Semester not found'}, status=404) + + course_slots = CourseSlot.objects.filter(semester_id=semester_info) + course_ids = course_slots.values_list('courses', flat=True) + courses = Courses.objects.filter(id__in=course_ids) + courses_map={} + for course in courses: + courses_map[course.id]=(course.credit) + students = Student.objects.filter(batch=batch, specialization=branch).order_by('id') + print(students.first().id_id,"studejt id") + + wb = Workbook() + ws = wb.active + ws.title = "Student Grades" + + + # ws.merge_cells(start_row=1, start_column=1, end_row=4, end_column=1) + # ws.merge_cells(start_row=1, start_column=2, end_row=4, end_column=2) + ws["A1"] = "S. No" + ws["B1"] = "Roll No" + for cell in ("A1", "B1"): + ws[cell].alignment = Alignment(horizontal="center", vertical="center") + ws[cell].font = Font(bold=True) + + + ws.column_dimensions[get_column_letter(1)].width = 12 + ws.column_dimensions[get_column_letter(2)].width = 18 + col_idx = 3 + for course in courses: + + ws.merge_cells(start_row=1, start_column=col_idx, end_row=1, end_column=col_idx + 1) + ws.merge_cells(start_row=2, start_column=col_idx, end_row=2, end_column=col_idx + 1) + ws.merge_cells(start_row=3, start_column=col_idx, end_row=3, end_column=col_idx + 1) + + ws.cell(row=1, column=col_idx).value = course.code + ws.cell(row=1, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=1, column=col_idx).font = Font(bold=True) + ws.cell(row=2, column=col_idx).value = course.name + ws.cell(row=2, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=2, column=col_idx).font = Font(bold=True) + + ws.cell(row=3, column=col_idx).value=course.credit + ws.cell(row=3, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=3, column=col_idx).font = Font(bold=True) + ws.cell(row=4, column=col_idx).value = "Grade" + ws.cell(row=4, column=col_idx + 1).value = "Remarks" + ws.cell(row=4, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=4, column=col_idx+1).alignment = Alignment(horizontal="center", vertical="center") + ws.column_dimensions[get_column_letter(col_idx)].width = 25 + ws.column_dimensions[get_column_letter(col_idx+1)].width = 25 + col_idx += 2 + + # ws.merge_cells(start_row=1, start_column=col_idx, end_row=4, end_column=col_idx) # SPI + ws.cell(row=1, column=col_idx).value = "SPI" + ws.cell(row=1, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=1, column=col_idx).font = Font(bold=True) + + # ws.merge_cells(start_row=1, start_column=col_idx + 1, end_row=4, end_column=col_idx + 1) # CPI + ws.cell(row=1, column=col_idx + 1).value = "CPI" + ws.cell(row=1, column=col_idx + 1).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=1, column=col_idx + 1).font = Font(bold=True) + + + row_idx = 5 + for idx, student in enumerate(students, start=1): + ws.cell(row=row_idx, column=1).value = idx + ws.cell(row=row_idx, column=2).value = student.id_id + + ws.cell(row=row_idx, column=1).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=row_idx, column=2).alignment = Alignment(horizontal="center", vertical="center") + student_grades = Student_grades.objects.filter( + roll_no=student.id_id, course_id_id__in=course_ids + ) + + grades_map = {} + for grade in student_grades: + grades_map[grade.course_id_id] = (grade.grade, grade.remarks,courses_map.get(grade.course_id_id) ) + + col_idx = 3 + gained_credit=0 + total_credit=0 + for course in courses: + grade, remark, credits = grades_map.get(course.id, ("N/A", "N/A",0)) + ws.cell(row=row_idx, column=col_idx).value = grade + ws.cell(row=row_idx, column=col_idx + 1).value = remark + ws.cell(row=row_idx, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=row_idx, column=col_idx+1).alignment = Alignment(horizontal="center", vertical="center") + if grade=="O" or grade=="A+": + gained_credit+=1*credits + total_credit+=credits + elif grade=="A": + gained_credit+=0.9*credits + total_credit+=credits + elif grade=="B+": + gained_credit+=0.8*credits + total_credit+=credits + elif grade=="B": + gained_credit+=0.7*credits + total_credit+=credits + elif grade=="C+": + gained_credit+=0.6*credits + total_credit+=credits + elif grade=="C": + gained_credit+=0.5*credits + total_credit+=credits + elif grade=="D+": + gained_credit+=0.4*credits + total_credit+=credits + elif grade=="D": + gained_credit+=0.3*credits + total_credit+=credits + elif grade=="F": + gained_credit+=0.2*credits + total_credit+=credits + + + col_idx += 2 + if total_credit==0 : + ws.cell(row=row_idx, column=col_idx).value =0 + else: + ws.cell(row=row_idx, column=col_idx).value = 10*(gained_credit/total_credit) + ws.cell(row=row_idx, column=col_idx + 1).value = 0 + ws.cell(row=row_idx, column=col_idx).alignment = Alignment(horizontal="center", vertical="center") + ws.cell(row=row_idx, column=col_idx+1).alignment = Alignment(horizontal="center", vertical="center") + + row_idx += 1 + + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = 'attachment; filename="student_grades.xlsx"' + wb.save(response) + return response + + except json.JSONDecodeError: + return JsonResponse({'error': 'Invalid JSON data'}, status=400) + except Exception as e: + traceback.print_exc() + return JsonResponse({'error': str(e)}, status=500) + + + return JsonResponse({'error': 'Invalid request method'}, status=405) \ No newline at end of file diff --git a/FusionIIIT/applications/globals/api/urls.py b/FusionIIIT/applications/globals/api/urls.py index e6683eac7..53dd23f95 100644 --- a/FusionIIIT/applications/globals/api/urls.py +++ b/FusionIIIT/applications/globals/api/urls.py @@ -6,6 +6,8 @@ url(r'^auth/login/', views.login, name='login-api'), url(r'^auth/logout/', views.logout, name='logout-api'), + url(r'^auth/me', views.auth_view, name='auth-api'), + url(r'^update-role/', views.update_last_selected_role, name='update_last_selected_role'), # generic profile endpoint #code of corresponding view is modifiedtemporary because of mismatched designations url(r'^profile/(?P.+)/', views.profile, name='profile-api'), @@ -14,9 +16,8 @@ url(r'^profile_update/', views.profile_update, name='update-profile-api'), url(r'^profile_delete/(?P[0-9]+)/', views.profile_delete, name='delete-profile-api'), - url(r'^dashboard/',views.dashboard,name='dashboard-api'), url(r'^notification/',views.notification,name='notification'), - url(r'^notification/read',views.NotificationRead,name='notifications-read') - - + url(r'^notificationread',views.NotificationRead,name='notifications-read'), + url(r'^notificationdelete',views.delete_notification,name='notifications-delete'), + url(r'^notificationunread',views.NotificationUnread,name='notifications-unread') ] diff --git a/FusionIIIT/applications/globals/api/views.py b/FusionIIIT/applications/globals/api/views.py index c8fd9f5b1..0aff2180d 100644 --- a/FusionIIIT/applications/globals/api/views.py +++ b/FusionIIIT/applications/globals/api/views.py @@ -18,7 +18,7 @@ from . import serializers from applications.globals.models import (ExtraInfo, Feedback, HoldsDesignation, - Issue, IssueImage, DepartmentInfo) + Issue, IssueImage, DepartmentInfo, ModuleAccess) from .utils import get_and_authenticate_user from notifications.models import Notification @@ -71,13 +71,15 @@ def logout(request): return Response(data=resp, status=status.HTTP_200_OK) @api_view(['GET']) -@permission_classes([IsAuthenticated]) -@authentication_classes([TokenAuthentication]) -def dashboard(request): +@permission_classes([AllowAny]) +def auth_view(request): user=request.user - name = request.user.first_name +"_"+ request.user.last_name + roll_no = request.user.username + extra_info = get_object_or_404(ExtraInfo, user=user) + last_selected_role = extra_info.last_selected_role + designation_list = list(HoldsDesignation.objects.all().filter(working = request.user).values_list('designation')) designation_id = [designation for designations in designation_list for designation in designations] designation_info = [] @@ -85,26 +87,36 @@ def dashboard(request): name_ = get_object_or_404(Designation, id = id) designation_info.append(str(name_.name)) - notifications=serializers.NotificationSerializer(request.user.notifications.all(),many=True).data - club_details= coordinator_club(request) + print(designation_info) + accessible_modules = {} + + for designation in designation_info: + module_access = ModuleAccess.objects.filter(designation__iexact=designation).first() + if module_access: + filtered_modules = {} + field_names = [field.name for field in ModuleAccess._meta.get_fields() if field.name not in ['id', 'designation']] + + for field_name in field_names: + filtered_modules[field_name] = getattr(module_access, field_name) + + accessible_modules[designation] = filtered_modules + resp={ - 'notifications':notifications, - 'desgination_info' : designation_info, - 'club_details' : club_details + 'designation_info' : designation_info, + 'name': name, + 'roll_no': roll_no, + 'accessible_modules': accessible_modules, + 'last_selected_role': last_selected_role } - + return Response(data=resp,status=status.HTTP_200_OK) @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([TokenAuthentication]) def notification(request): - - print(request) notifications=serializers.NotificationSerializer(request.user.notifications.all(),many=True).data - print("get") - print(notifications) resp={ 'notifications':notifications, @@ -112,6 +124,21 @@ def notification(request): return Response(data=resp,status=status.HTTP_200_OK) +@api_view(['PATCH']) +@permission_classes([IsAuthenticated]) +def update_last_selected_role(request): + new_role = request.data.get('last_selected_role') + + if new_role is None: + return Response({'error': 'last_selected_role is required'}, status=status.HTTP_400_BAD_REQUEST) + + extra_info = get_object_or_404(ExtraInfo, user=request.user) + + extra_info.last_selected_role = new_role + extra_info.save() + + return Response({'message': 'last_selected_role updated successfully'}, status=status.HTTP_200_OK) + @api_view(['GET']) def profile(request, username=None): user = get_object_or_404(User, username=username) if username else request.user @@ -306,4 +333,47 @@ def NotificationRead(request): response ={ 'error':'Failed, notification is not marked as seen.' } - return Response(response,status=status.HTTP_404_NOT_FOUND) \ No newline at end of file + return Response(response,status=status.HTTP_404_NOT_FOUND) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +@authentication_classes([TokenAuthentication]) +def NotificationUnread(request): + try: + notifId = int(request.data['id']) + user = request.user + notification = get_object_or_404(Notification, recipient=user, id=notifId) + if not notification.unread: + notification.unread = True + notification.save() + response = { + 'message': 'Notification successfully marked as unread.' + } + return Response(response, status=status.HTTP_200_OK) + except: + response = { + 'error': 'Failed to mark the notification as unread.' + } + return Response(response, status=status.HTTP_404_NOT_FOUND) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +@authentication_classes([TokenAuthentication]) +def delete_notification(request): + try: + notifId = int(request.data['id']) + notification = get_object_or_404(Notification, recipient=request.user, id=notifId) + + notification.deleted = True + notification.save() + + response = { + 'message': 'Notification marked as deleted.' + } + return Response(response, status=status.HTTP_200_OK) + except Exception as e: + response = { + 'error': 'Failed to mark the notification as deleted.', + 'details': str(e) + } + return Response(response, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/FusionIIIT/applications/globals/migrations/0002_auto_20241007_2302.py b/FusionIIIT/applications/globals/migrations/0002_auto_20241007_2302.py new file mode 100644 index 000000000..0cf484df2 --- /dev/null +++ b/FusionIIIT/applications/globals/migrations/0002_auto_20241007_2302.py @@ -0,0 +1,44 @@ +# Generated by Django 3.1.5 on 2024-10-07 23:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('globals', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ModuleAccess', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('designation', models.CharField(max_length=155)), + ('program_and_curriculum', models.BooleanField(default=False)), + ('course_registration', models.BooleanField(default=False)), + ('course_management', models.BooleanField(default=False)), + ('other_academics', models.BooleanField(default=False)), + ('spacs', models.BooleanField(default=False)), + ('department', models.BooleanField(default=False)), + ('examinations', models.BooleanField(default=False)), + ('hr', models.BooleanField(default=False)), + ('iwd', models.BooleanField(default=False)), + ('complaint_management', models.BooleanField(default=False)), + ('fts', models.BooleanField(default=False)), + ('purchase_and_store', models.BooleanField(default=False)), + ('rspc', models.BooleanField(default=False)), + ('hostel_management', models.BooleanField(default=False)), + ('mess_management', models.BooleanField(default=False)), + ('gymkhana', models.BooleanField(default=False)), + ('placement_cell', models.BooleanField(default=False)), + ('visitor_hostel', models.BooleanField(default=False)), + ('phc', models.BooleanField(default=False)), + ], + ), + migrations.AddField( + model_name='extrainfo', + name='last_selected_role', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/FusionIIIT/applications/globals/migrations/0002_moduleaccess.py b/FusionIIIT/applications/globals/migrations/0002_moduleaccess.py new file mode 100644 index 000000000..75fd77a4f --- /dev/null +++ b/FusionIIIT/applications/globals/migrations/0002_moduleaccess.py @@ -0,0 +1,39 @@ +# Generated by Django 3.1.5 on 2024-10-20 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('globals', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ModuleAccess', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('designation', models.CharField(max_length=155)), + ('program_and_curriculum', models.BooleanField(default=False)), + ('course_registration', models.BooleanField(default=False)), + ('course_management', models.BooleanField(default=False)), + ('other_academics', models.BooleanField(default=False)), + ('spacs', models.BooleanField(default=False)), + ('department', models.BooleanField(default=False)), + ('examinations', models.BooleanField(default=False)), + ('hr', models.BooleanField(default=False)), + ('iwd', models.BooleanField(default=False)), + ('complaint_management', models.BooleanField(default=False)), + ('fts', models.BooleanField(default=False)), + ('purchase_and_store', models.BooleanField(default=False)), + ('rspc', models.BooleanField(default=False)), + ('hostel_management', models.BooleanField(default=False)), + ('mess_management', models.BooleanField(default=False)), + ('gymkhana', models.BooleanField(default=False)), + ('placement_cell', models.BooleanField(default=False)), + ('visitor_hostel', models.BooleanField(default=False)), + ('phc', models.BooleanField(default=False)), + ], + ), + ] diff --git a/FusionIIIT/applications/globals/models.py b/FusionIIIT/applications/globals/models.py index bc95a3042..ee3397eaf 100644 --- a/FusionIIIT/applications/globals/models.py +++ b/FusionIIIT/applications/globals/models.py @@ -65,10 +65,10 @@ class Constants: ('academic', 'Academic Designation'), ('administrative', 'Administrative Designation'), ) - USER_STATUS = { + USER_STATUS = ( ("NEW", "NEW"), ("PRESENT", "PRESENT"), - } + ) class Designation(models.Model): @@ -152,6 +152,7 @@ class ExtraInfo(models.Model): null=True, blank=True, upload_to='globals/profile_pictures') about_me = models.TextField(default='NA', max_length=1000, blank=True) date_modified = models.DateTimeField('date_updated', blank=True, null=True) + last_selected_role = models.CharField(max_length=20, null=True, blank=True) @property def age(self): @@ -224,7 +225,8 @@ class Faculty(models.Model): def __str__(self): - return str(self.id) + # return str(self.id) + return f"{self.id} - {self.id.user.first_name} {self.id.user.last_name}" """ Feedback and bug report models start""" diff --git a/FusionIIIT/applications/hostel_management/urls.py b/FusionIIIT/applications/hostel_management/urls.py index 95fcdeeb8..28c1915c2 100644 --- a/FusionIIIT/applications/hostel_management/urls.py +++ b/FusionIIIT/applications/hostel_management/urls.py @@ -12,7 +12,7 @@ path('admin/', admin.site.urls), #Home path('', views.hostel_view, name="hostel_view"), - path('/hello', views.hostel_view, name="hello"), + path('hello', views.hostel_view, name="hello"), #Notice Board path('notice_form/', views.notice_board, name="notice_board"), diff --git a/FusionIIIT/applications/hr2/migrations/0002_auto_20241012_1459.py b/FusionIIIT/applications/hr2/migrations/0002_auto_20241012_1459.py new file mode 100644 index 000000000..be4738fe2 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0002_auto_20241012_1459.py @@ -0,0 +1,64 @@ +# Generated by Django 3.1.5 on 2024-10-12 14:59 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='appraisalform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='amountRequired', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='pfNo', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdareimbursementform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdareimbursementform', + name='pfNo', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='empconfidentialdetails', + name='aadhar_no', + field=models.BigIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(999999999999), django.core.validators.MinValueValidator(99999999999)]), + ), + migrations.AlterField( + model_name='leaveform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='leaveform', + name='pfNo', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='ltcform', + name='pfNo', + field=models.IntegerField(), + ), + ] diff --git a/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py b/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py new file mode 100644 index 000000000..5b99015f7 --- /dev/null +++ b/FusionIIIT/applications/hr2/migrations/0002_auto_20241020_1126.py @@ -0,0 +1,64 @@ +# Generated by Django 3.1.5 on 2024-10-20 11:26 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hr2', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='appraisalform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='amountRequired', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdaadvanceform', + name='pfNo', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdareimbursementform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='cpdareimbursementform', + name='pfNo', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='empconfidentialdetails', + name='aadhar_no', + field=models.BigIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(999999999999), django.core.validators.MinValueValidator(99999999999)]), + ), + migrations.AlterField( + model_name='leaveform', + name='employeeId', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='leaveform', + name='pfNo', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='ltcform', + name='pfNo', + field=models.IntegerField(), + ), + ] diff --git a/FusionIIIT/applications/hr2/models.py b/FusionIIIT/applications/hr2/models.py index 01ca223c7..e225ca076 100644 --- a/FusionIIIT/applications/hr2/models.py +++ b/FusionIIIT/applications/hr2/models.py @@ -78,7 +78,7 @@ class EmpConfidentialDetails(models.Model): table for employee confidential details """ extra_info = models.OneToOneField(ExtraInfo, on_delete=models.CASCADE) - aadhar_no = models.BigIntegerField(default=0, max_length=12, + aadhar_no = models.BigIntegerField(default=0, validators=[MaxValueValidator(999999999999),MinValueValidator(99999999999)]) maritial_status = models.CharField( @@ -149,7 +149,7 @@ class LTCform(models.Model): employeeId = models.IntegerField() name = models.CharField(max_length=100, null=True) blockYear = models.TextField() # - pfNo = models.IntegerField(max_length=50) + pfNo = models.IntegerField() basicPaySalary = models.IntegerField(null=True) designation = models.CharField(max_length=50) departmentInfo = models.CharField(max_length=50) @@ -181,12 +181,12 @@ class LTCform(models.Model): class CPDAAdvanceform(models.Model): id = models.AutoField(primary_key=True) - employeeId = models.IntegerField(max_length=22, null=True) + employeeId = models.IntegerField(null=True) name = models.CharField(max_length=40,null=True) designation = models.CharField(max_length=40,null=True) - pfNo = models.IntegerField(max_length=30,null=True) + pfNo = models.IntegerField(null=True) purpose = models.TextField(max_length=40, null=True) - amountRequired = models.IntegerField(max_length=30,null=True) + amountRequired = models.IntegerField(null=True) advanceDueAdjustment = models.DecimalField(max_digits=10, decimal_places=2, null=True,blank=True) submissionDate = models.DateField(blank=True, null=True) @@ -202,11 +202,11 @@ class CPDAAdvanceform(models.Model): class LeaveForm(models.Model): id = models.AutoField(primary_key=True) - employeeId = models.IntegerField(max_length=22,null=True) + employeeId = models.IntegerField(null=True) name = models.CharField(max_length=40,null=True) designation = models.CharField(max_length=40,null=True) submissionDate = models.DateField(blank=True, null=True) - pfNo = models.IntegerField(max_length=30,null=True) + pfNo = models.IntegerField(null=True) departmentInfo = models.CharField(max_length=40,null=True) natureOfLeave = models.TextField(max_length=40,null=True) leaveStartDate = models.DateField(blank=True, null=True) @@ -237,7 +237,7 @@ class LeaveBalance(models.Model): class Appraisalform(models.Model): id = models.AutoField(primary_key=True) - employeeId = models.IntegerField(max_length=22,null=True) + employeeId = models.IntegerField(null=True) name = models.CharField(max_length=22) designation = models.CharField(max_length=50) disciplineInfo = models.CharField(max_length=22, null=True) @@ -273,10 +273,10 @@ class Appraisalform(models.Model): class CPDAReimbursementform(models.Model): id = models.AutoField(primary_key=True) - employeeId = models.IntegerField(max_length=22,null=True) + employeeId = models.IntegerField(null=True) name = models.CharField(max_length=50) designation = models.CharField(max_length=50) - pfNo = models.IntegerField(max_length=20) + pfNo = models.IntegerField() advanceTaken = models.IntegerField() purpose = models.TextField() adjustmentSubmitted = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) diff --git a/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241012_1459.py b/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241012_1459.py new file mode 100644 index 000000000..546d6c629 --- /dev/null +++ b/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241012_1459.py @@ -0,0 +1,89 @@ +# Generated by Django 3.1.5 on 2024-10-12 14:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('iwdModuleV2', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='pageonedetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AddField( + model_name='pagethreedetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AddField( + model_name='pagetwodetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='addendum', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='agreement', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='corrigendumtable', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='financialbiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='letterofintentdetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='nooftechnicalbidtimes', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='pageonedetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='pagethreedetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='pagetwodetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='prebiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='technicalbiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='workorderform', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + ] diff --git a/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241020_1126.py b/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241020_1126.py new file mode 100644 index 000000000..cbadd4cd6 --- /dev/null +++ b/FusionIIIT/applications/iwdModuleV2/migrations/0002_auto_20241020_1126.py @@ -0,0 +1,89 @@ +# Generated by Django 3.1.5 on 2024-10-20 11:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('iwdModuleV2', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='pageonedetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AddField( + model_name='pagethreedetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AddField( + model_name='pagetwodetails', + name='page_id', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='addendum', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='agreement', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='corrigendumtable', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='financialbiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='letterofintentdetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='nooftechnicalbidtimes', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='pageonedetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='pagethreedetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='pagetwodetails', + name='id', + field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='prebiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='technicalbiddetails', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + migrations.AlterField( + model_name='workorderform', + name='key', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='iwdModuleV2.projects'), + ), + ] diff --git a/FusionIIIT/applications/iwdModuleV2/models.py b/FusionIIIT/applications/iwdModuleV2/models.py index 9a3337987..9c79aa21c 100644 --- a/FusionIIIT/applications/iwdModuleV2/models.py +++ b/FusionIIIT/applications/iwdModuleV2/models.py @@ -9,7 +9,7 @@ class Projects(models.Model): class PageOneDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) + page_id = models.OneToOneField(Projects, on_delete=models.CASCADE, null=True) aESFile = models.FileField(null=True) dASA = models.DateField(null=True) nitNiqNo = models.IntegerField(null=True) @@ -31,7 +31,7 @@ class AESDetails(models.Model): class PageTwoDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) + page_id = models.OneToOneField(Projects, on_delete=models.CASCADE, null=True) corrigendum = models.FileField(null=True) addendum = models.FileField(null=True) preBidMeetingDetails = models.FileField(null=True) @@ -46,7 +46,7 @@ class PageTwoDetails(models.Model): class CorrigendumTable(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) issueDate = models.DateField() nitNo = models.IntegerField() name = models.CharField(max_length=200) @@ -59,7 +59,7 @@ class CorrigendumTable(models.Model): class Addendum(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) issueDate = models.DateField() nitNiqNo = models.IntegerField() name = models.CharField(max_length=200) @@ -68,7 +68,7 @@ class Addendum(models.Model): class PreBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) sNo = models.CharField(max_length=200) nameOfParticipants = models.CharField(max_length=200) issuesRaised = models.CharField(max_length=200) @@ -76,7 +76,7 @@ class PreBidDetails(models.Model): class TechnicalBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) sNo = models.CharField(max_length=200) requirements = models.CharField(max_length=200) @@ -88,7 +88,7 @@ class TechnicalBidContractorDetails(models.Model): class FinancialBidDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) sNo = models.CharField(max_length=200) description = models.CharField(max_length=200) @@ -103,7 +103,7 @@ class FinancialContractorDetails(models.Model): class LetterOfIntentDetails(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) nitNiqNo = models.IntegerField() dateOfOpening = models.DateField() agency = models.CharField(max_length=200) @@ -112,7 +112,7 @@ class LetterOfIntentDetails(models.Model): class WorkOrderForm(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) issueDate = models.DateField() nitNiqNo = models.IntegerField() agency = models.CharField(max_length=200) @@ -127,7 +127,7 @@ class WorkOrderForm(models.Model): class Agreement(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) date = models.DateField() agencyName = models.CharField(max_length=200) workName = models.CharField(max_length=200) @@ -143,7 +143,7 @@ class Milestones(models.Model): class PageThreeDetails(models.Model): - id = models.ForeignKey(Projects, on_delete=models.CASCADE, primary_key=True) + page_id = models.OneToOneField(Projects, on_delete=models.CASCADE, null=True) extensionOfTime = models.FileField() actualCostOfBuilding = models.IntegerField() @@ -157,7 +157,7 @@ class ExtensionOfTimeDetails(models.Model): class NoOfTechnicalBidTimes(models.Model): - key = models.ForeignKey(Projects, on_delete=models.CASCADE, unique=True) + key = models.OneToOneField(Projects, on_delete=models.CASCADE) number = models.IntegerField() class Requests(models.Model): diff --git a/FusionIIIT/applications/iwdModuleV2/views.py b/FusionIIIT/applications/iwdModuleV2/views.py index 1f999ebd9..be7042499 100644 --- a/FusionIIIT/applications/iwdModuleV2/views.py +++ b/FusionIIIT/applications/iwdModuleV2/views.py @@ -360,13 +360,13 @@ def page1View(request): if request.POST: request.session['projectId'] = request.POST['id'] projectPageOne = PageOneDetails.objects.get( - id=Projects.objects.get(id=request.session['projectId'])) + id=Projects.objects.get(page_id=request.session['projectId'])) return render(request, 'iwdModuleV2/Page1.html', {'x': projectPageOne}) def page2View(request): projectPageTwo = PageTwoDetails.objects.get( - id=Projects.objects.get(id=request.session['projectId'])) + id=Projects.objects.get(page_id=request.session['projectId'])) return render(request, 'iwdModuleV2/Page2.html', {'x': projectPageTwo}) @@ -445,7 +445,7 @@ def milestoneView(request): def page3View(request): pageThreeDetails = PageThreeDetails.objects.get( - id=Projects.objects.get(id=request.session['projectId'])) + id=Projects.objects.get(page_id=request.session['projectId'])) return render(request, 'iwdModuleV2/Page3.html', {'x': pageThreeDetails}) diff --git a/FusionIIIT/applications/online_cms/migrations/0002_auto_20241012_1459.py b/FusionIIIT/applications/online_cms/migrations/0002_auto_20241012_1459.py new file mode 100644 index 000000000..d689e8177 --- /dev/null +++ b/FusionIIIT/applications/online_cms/migrations/0002_auto_20241012_1459.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.5 on 2024-10-12 14:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('academic_information', '0001_initial'), + ('online_cms', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='attendance', + name='no_of_attendance', + field=models.IntegerField(default=1), + ), + migrations.AlterField( + model_name='attendance', + name='present', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='gradingscheme', + name='type_of_evaluation', + field=models.CharField(max_length=100), + ), + migrations.CreateModel( + name='StudentEvaluation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('marks', models.DecimalField(decimal_places=2, max_digits=10, null=True)), + ('total_marks', models.DecimalField(decimal_places=2, default=0, max_digits=10)), + ('evaluation_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='online_cms.gradingscheme')), + ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), + ], + ), + ] diff --git a/FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py b/FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py new file mode 100644 index 000000000..659201064 --- /dev/null +++ b/FusionIIIT/applications/online_cms/migrations/0002_auto_20241023_1002.py @@ -0,0 +1,59 @@ +# Generated by Django 3.1.5 on 2024-10-23 10:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('academic_information', '0001_initial'), + ('online_cms', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='student_grades', + name='total_marks', + ), + migrations.AddField( + model_name='attendance', + name='no_of_attendance', + field=models.IntegerField(default=1), + ), + migrations.AddField( + model_name='student_grades', + name='reSubmit', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='student_grades', + name='remarks', + field=models.CharField(max_length=500, null=True), + ), + migrations.AddField( + model_name='student_grades', + name='verified', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='attendance', + name='present', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='gradingscheme', + name='type_of_evaluation', + field=models.CharField(max_length=100), + ), + migrations.CreateModel( + name='StudentEvaluation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('marks', models.DecimalField(decimal_places=2, max_digits=10, null=True)), + ('total_marks', models.DecimalField(decimal_places=2, default=0, max_digits=10)), + ('evaluation_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='online_cms.gradingscheme')), + ('student_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='academic_information.student')), + ], + ), + ] diff --git a/FusionIIIT/applications/online_cms/models.py b/FusionIIIT/applications/online_cms/models.py index 3a63aca08..98d68b24c 100644 --- a/FusionIIIT/applications/online_cms/models.py +++ b/FusionIIIT/applications/online_cms/models.py @@ -267,10 +267,11 @@ class Student_grades(models.Model): semester = models.IntegerField(default=1) year = models.IntegerField(default=2016) roll_no = models.TextField(max_length=2000) - total_marks = models.DecimalField(max_digits=10, decimal_places=2, default=0) grade = models.TextField(max_length=2000) batch = models.IntegerField(default=2021) - + remarks = models.CharField(max_length=500,null=True) + verified = models.BooleanField(default=False) + reSubmit = models.BooleanField(default=True) def __str__(self): return '{} - {}'.format(self.pk, self.course_id) diff --git a/FusionIIIT/applications/programme_curriculum/filters.py b/FusionIIIT/applications/programme_curriculum/filters.py index d1da0cca5..6499607f8 100644 --- a/FusionIIIT/applications/programme_curriculum/filters.py +++ b/FusionIIIT/applications/programme_curriculum/filters.py @@ -1,6 +1,7 @@ import django_filters from django import forms -from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot, PROGRAMME_CATEGORY_CHOICES +from django.db.models import Q +from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot, PROGRAMME_CATEGORY_CHOICES,CourseInstructor class CourseFilter(django_filters.FilterSet): class Meta: @@ -40,4 +41,34 @@ class Meta: 'name' : forms.TextInput(attrs={'placeholder': 'Course/Project Name','max_length': 100,}), 'name' : django_filters.CharFilter(forms.TextInput(attrs={'placeholder': 'Course/Project Name','max_length': 100,})), 'code' : django_filters.CharFilter(forms.TextInput(attrs={'placeholder': 'Version','max_length': 10,})), - } \ No newline at end of file + } + +class CourseInstructorFilter(django_filters.FilterSet): + course_id = django_filters.CharFilter( + field_name='course_id__code', + lookup_expr='icontains', + label='Course Code' + ) + + instructor_name = django_filters.CharFilter( + method='filter_instructor_name', + label='Instructor Name' + ) + + year = django_filters.NumberFilter( + field_name='year', + lookup_expr='exact', + label='Year' + ) + class Meta: + model = CourseInstructor + fields = ['course_id', 'instructor_name', 'year'] + + def filter_instructor_name(self, queryset, name, value): + """ + Custom filter method to filter by first_name and last_name. + """ + return queryset.filter( + Q(instructor_id__id__user__first_name__icontains=value) | + Q(instructor_id__id__user__last_name__icontains=value) + ) diff --git a/FusionIIIT/applications/programme_curriculum/forms.py b/FusionIIIT/applications/programme_curriculum/forms.py index 394828004..0698d514a 100644 --- a/FusionIIIT/applications/programme_curriculum/forms.py +++ b/FusionIIIT/applications/programme_curriculum/forms.py @@ -3,12 +3,13 @@ from django.forms import ModelForm, widgets from django.forms import Form, ValidationError from django.forms.models import ModelChoiceField -from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot, PROGRAMME_CATEGORY_CHOICES,NewProposalFile,Proposal_Tracking +from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot, PROGRAMME_CATEGORY_CHOICES,NewProposalFile,Proposal_Tracking, CourseInstructor from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import User from applications.globals.models import (DepartmentInfo, Designation,ExtraInfo, Faculty, HoldsDesignation) from applications.filetracking.sdk.methods import * - +from django.db.models import Q +from datetime import datetime class ProgrammeForm(ModelForm): class Meta: @@ -38,6 +39,20 @@ class Meta: 'programmes': 'Link Programmes to this Disciplines', 'acronym' : 'Enter Acronym' } + def __init__(self, *args, **kwargs): + super(DisciplineForm, self).__init__(*args, **kwargs) + + # Get the current discipline instance + discipline = kwargs.get('instance', None) + + if discipline: + # Show programmes that are either unlinked or linked to the current discipline + self.fields['programmes'].queryset = Programme.objects.filter( + Q(discipline__isnull=True) | Q(discipline=discipline) + ) + else: + # Show only programmes that are unlinked (no discipline assigned) + self.fields['programmes'].queryset = Programme.objects.filter(discipline__isnull=True) class CurriculumForm(ModelForm): @@ -160,6 +175,7 @@ class Meta: 'percent_project' : forms.NumberInput(attrs={'placeholder': '%'}, ), 'percent_lab_evaluation' : forms.NumberInput(attrs={'placeholder': '%'}, ), 'percent_course_attendance' : forms.NumberInput(attrs={'placeholder': '%'}, ), + 'max_seats' : forms.NumberInput(attrs={'placeholder': 'max_seats',}, ), } labels = { 'code' : 'Course Code', @@ -183,7 +199,8 @@ class Meta: 'percent_lab_evaluation' : 'percent_lab_evaluation', 'percent_course_attendance' : 'percent_course_attendance', 'working_course' : 'working_course', - 'disciplines' : 'disciplines' + 'disciplines' : 'disciplines', + 'max_seats' : 'max_seats' } @@ -203,6 +220,18 @@ class Meta: 'year' : 'Batch Year', 'curriculum' : 'Select Curriculum For Batch Students', } + def __init__(self, *args, **kwargs): + super(BatchForm, self).__init__(*args, **kwargs) + + # Get the list of curriculum ids that are already assigned to batches (excluding NULL values) + assigned_curriculum_ids = Batch.objects.filter(curriculum__isnull=False).values_list('curriculum', flat=True) + + # Exclude curriculums already in use + available_curriculums = Curriculum.objects.exclude(id__in=assigned_curriculum_ids) + + # Add an empty option (blank choice) at the start of the curriculum choices + self.fields['curriculum'].queryset = available_curriculums + self.fields['curriculum'].empty_label = "Select Curriculum" # This adds a blank option with a label class CourseSlotForm(ModelForm): @@ -356,7 +385,44 @@ def clean(self): return self.cleaned_data - + +class CourseInstructorForm(forms.ModelForm): + next_year = datetime.now().year +1 + course_id = forms.ModelChoiceField( + queryset=Course.objects.all(), + label="Select Course", + empty_label="Choose a course", + widget=forms.Select(attrs={'class': 'ui fluid search selection dropdown'}) + ) + instructor_id = forms.ModelChoiceField( + # queryset=ExtraInfo.objects.filter(user_type='faculty'), + queryset = Faculty.objects.all(), + label="Select Instructor", + empty_label="Choose an instructor", + widget=forms.Select(attrs={'class': 'ui fluid search selection dropdown'}) + ) + + year = forms.ChoiceField( + choices=[('', 'Choose a year')] + [(year, year) for year in Batch.objects.values_list('year', flat=True).distinct()]+[(next_year, next_year)], + label="Select Year", + widget=forms.Select(attrs={'class': 'ui fluid search selection dropdown'}) + ) + semester_no = forms.ChoiceField( + choices=[('', 'Choose a semester')] + [(i, str(i)) for i in range(1, 9)], # Choices from 1 to 8 + label="Select Semester Number", + # empty_label="Choose a semester", + widget=forms.Select(attrs={'class': 'ui fluid search selection dropdown'}) + ) + class Meta: + model = CourseInstructor + fields = ['course_id', 'instructor_id', 'year', 'semester_no'] + + # def __init__(self, *args, **kwargs): + # super().__init__(*args, **kwargs) + # # Query all unique years from the Batch table + # unique_years = Batch.objects.values_list('year', flat=True).distinct() + # # Set the choices for the 'year' field dynamically + # self.fields['year'].choices = [(year, year) for year in unique_years] # def sed(self): # r_id = self.cleaned_data.get('receive_id') diff --git a/FusionIIIT/applications/programme_curriculum/migrations/0002_course_max_seats.py b/FusionIIIT/applications/programme_curriculum/migrations/0002_course_max_seats.py new file mode 100644 index 000000000..75700de21 --- /dev/null +++ b/FusionIIIT/applications/programme_curriculum/migrations/0002_course_max_seats.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2024-10-23 00:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('programme_curriculum', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='max_seats', + field=models.IntegerField(default=90), + ), + ] diff --git a/FusionIIIT/applications/programme_curriculum/migrations/0003_auto_20241115_1545.py b/FusionIIIT/applications/programme_curriculum/migrations/0003_auto_20241115_1545.py new file mode 100644 index 000000000..a30ce9892 --- /dev/null +++ b/FusionIIIT/applications/programme_curriculum/migrations/0003_auto_20241115_1545.py @@ -0,0 +1,39 @@ +# Generated by Django 3.1.5 on 2024-11-15 15:45 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('globals', '0002_moduleaccess'), + ('programme_curriculum', '0002_course_max_seats'), + ] + + operations = [ + migrations.AddField( + model_name='courseinstructor', + name='semester_no', + field=models.IntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(8)]), + ), + migrations.AddField( + model_name='courseinstructor', + name='year', + field=models.IntegerField(default=2024), + ), + migrations.AlterField( + model_name='courseinstructor', + name='instructor_id', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='globals.faculty'), + ), + migrations.AlterUniqueTogether( + name='courseinstructor', + unique_together={('course_id', 'instructor_id', 'year')}, + ), + migrations.RemoveField( + model_name='courseinstructor', + name='batch_id', + ), + ] diff --git a/FusionIIIT/applications/programme_curriculum/migrations/0004_auto_20241117_0137.py b/FusionIIIT/applications/programme_curriculum/migrations/0004_auto_20241117_0137.py new file mode 100644 index 000000000..8a8d84741 --- /dev/null +++ b/FusionIIIT/applications/programme_curriculum/migrations/0004_auto_20241117_0137.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2024-11-17 01:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('programme_curriculum', '0003_auto_20241115_1545'), + ] + + operations = [ + migrations.AlterField( + model_name='courseslot', + name='type', + field=models.CharField(choices=[('Professional Core', 'Professional Core'), ('Professional Elective', 'Professional Elective'), ('Professional Lab', 'Professional Lab'), ('Engineering Science', 'Engineering Science'), ('Natural Science', 'Natural Science'), ('Humanities', 'Humanities'), ('Design', 'Design'), ('Manufacturing', 'Manufacturing'), ('Management Science', 'Management Science'), ('Open Elective', 'Open Elective'), ('Swayam', 'Swayam'), ('Project', 'Project'), ('Optional', 'Optional'), ('Backlog', 'Backlog'), ('Others', 'Others')], max_length=70), + ), + ] diff --git a/FusionIIIT/applications/programme_curriculum/models.py b/FusionIIIT/applications/programme_curriculum/models.py index 655994c1c..c3f5518b1 100644 --- a/FusionIIIT/applications/programme_curriculum/models.py +++ b/FusionIIIT/applications/programme_curriculum/models.py @@ -6,7 +6,7 @@ from django.db.models import CheckConstraint, Q, F from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError -from applications.globals.models import ExtraInfo +from applications.globals.models import ExtraInfo,Faculty from django.core.validators import MinValueValidator, MaxValueValidator, DecimalValidator from applications.globals.models import (DepartmentInfo, Designation, ExtraInfo, Faculty, HoldsDesignation) @@ -35,6 +35,7 @@ ('Swayam', 'Swayam'), ('Project', 'Project'), ('Optional', 'Optional'), + ('Backlog', 'Backlog'), ('Others', 'Others') ] @@ -261,7 +262,7 @@ class Course(models.Model): working_course = models.BooleanField(default=True) disciplines = models.ManyToManyField(Discipline, blank=True) latest_version = models.BooleanField(default=True) - + max_seats = models.IntegerField(default=90) class Meta: unique_together = ('code', 'version') @@ -349,12 +350,16 @@ def for_batches(self): class CourseInstructor(models.Model): course_id = models.ForeignKey(Course, on_delete=models.CASCADE) - instructor_id = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) - batch_id = models.ForeignKey(Batch, on_delete=models.CASCADE, default=1) - # change extra info to faculty(globals) + # instructor_id = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) + instructor_id = models.ForeignKey(Faculty, on_delete=models.CASCADE) + year = models.IntegerField(default=datetime.date.today().year, null=False) # Default to the current year + semester_no = models.IntegerField( + default=1, # Set default semester as 1 + validators=[MinValueValidator(1), MaxValueValidator(8)] # Constraint for semesters + ) class Meta: - unique_together = ('course_id', 'instructor_id', 'batch_id') + unique_together = ('course_id', 'instructor_id', 'year') #new class NewProposalFile(models.Model): diff --git a/FusionIIIT/applications/programme_curriculum/urls.py b/FusionIIIT/applications/programme_curriculum/urls.py index 4c06b2b1b..d89953767 100644 --- a/FusionIIIT/applications/programme_curriculum/urls.py +++ b/FusionIIIT/applications/programme_curriculum/urls.py @@ -31,6 +31,7 @@ path('admin_course//', views.admin_view_a_course, name='admin_view_a_course'), path('admin_disciplines/', views.admin_view_all_discplines, name='admin_view_all_discplines'), path('admin_batches/', views.admin_view_all_batches, name='admin_view_all_batches'), + path('admin_instructor/',views.admin_view_all_course_instructor,name='admin_view_all_course_instructor'), path('admin_add_programme/', views.add_programme_form, name='add_programme_form'), path('admin_add_discipline/', views.add_discipline_form, name='add_discipline_form'), @@ -39,7 +40,9 @@ path('admin_add_courseslot/', views.add_courseslot_form, name='add_courseslot_form'), path('admin_add_course/', views.add_course_form, name='add_course_form'), path('admin_add_batch/', views.add_batch_form, name='add_batch_form'), + path('admin_add_course_instructor/', views.add_course_instructor, name='add_course_instructor'), + path('admin_update_course_instructor//', views.update_course_instructor_form, name='update_course_instructor_form'), path('admin_update_course//', views.update_course_form, name='update_course_form'), path('admin_edit_curriculum//', views.edit_curriculum_form, name='edit_curriculum_form'), path('admin_edit_programme//', views.edit_programme_form, name='edit_programme_form'), diff --git a/FusionIIIT/applications/programme_curriculum/views.py b/FusionIIIT/applications/programme_curriculum/views.py index 4ea51e609..a7ba330a7 100644 --- a/FusionIIIT/applications/programme_curriculum/views.py +++ b/FusionIIIT/applications/programme_curriculum/views.py @@ -6,9 +6,9 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot,NewProposalFile,Proposal_Tracking -from .forms import ProgrammeForm, DisciplineForm, CurriculumForm, SemesterForm, CourseForm, BatchForm, CourseSlotForm, ReplicateCurriculumForm,NewCourseProposalFile,CourseProposalTrackingFile -from .filters import CourseFilter, BatchFilter, CurriculumFilter +from .models import Programme, Discipline, Curriculum, Semester, Course, Batch, CourseSlot,NewProposalFile,Proposal_Tracking,CourseInstructor +from .forms import ProgrammeForm, DisciplineForm, CurriculumForm, SemesterForm, CourseForm, BatchForm, CourseSlotForm, ReplicateCurriculumForm,NewCourseProposalFile,CourseProposalTrackingFile, CourseInstructorForm +from .filters import CourseFilter, BatchFilter, CurriculumFilter,CourseInstructorFilter from django.db import IntegrityError from django.utils import timezone @@ -17,6 +17,33 @@ from applications.globals.models import (DepartmentInfo, Designation,ExtraInfo, Faculty, HoldsDesignation) # ------------module-functions---------------# + + + +@login_required(login_url='/accounts/login') +def admin_view_all_course_instructor(request): + # Fetch all records from the CourseInstructor table + course_instructors = CourseInstructor.objects.all() + + # Passing the data to the template + context = { + 'course_instructors': course_instructors, + } + + return render(request, 'programme_curriculum/acad_admin/admin_view_all_course_instructor.html', context) +@login_required(login_url='/accounts/login') +def update_course_instructor_form(request, id): + + instructor = get_object_or_404(CourseInstructor, id=id) + course_instructors = CourseInstructor.objects.all() + + # Passing the data to the template + context = { + 'course_instructors': course_instructors, + } + # Handle the update logic here + return render(request, 'programme_curriculum/acad_admin/admin_view_all_course_instructor.html', context) + @login_required(login_url='/accounts/login') def programme_curriculum(request): """ @@ -225,6 +252,7 @@ def view_a_courseslot(request, courseslot_id): elif 'hod' in request.session['currentDesignationSelected'].lower(): url+='faculty/' course_slot = get_object_or_404(CourseSlot, Q(id=courseslot_id)) + notifs = request.user.notifications.all() return render(request, url+'view_a_courseslot.html', {'course_slot': course_slot,'notifications': notifs,}) @@ -438,7 +466,7 @@ def admin_view_semesters_of_a_curriculum(request, curriculum_id): transpose_semester_slots = list(zip(*semester_slots)) - all_batches = Batch.objects.filter(running_batch=True).exclude(curriculum=curriculum_id).order_by('year') + all_batches = Batch.objects.filter(running_batch=True, curriculum__isnull=True).order_by('year') return render(request, 'programme_curriculum/acad_admin/admin_view_semesters_of_a_curriculum.html', {'curriculum': curriculum, 'semesters': semesters, 'semester_slots': transpose_semester_slots, 'semester_credits': semester_credits, 'all_batches':all_batches}) @@ -817,7 +845,7 @@ def update_course_form(request, course_id): ver=0 if(new_course.version>previous.version): # Check if a course with the same values (except version, latest_version, disciplines, and pre_requisit_courses) already exists - old_course=Course.objects.filter(code=new_course.code, name=new_course.name, credit=new_course.credit, lecture_hours=new_course.lecture_hours, tutorial_hours=new_course.tutorial_hours, pratical_hours=new_course.pratical_hours, discussion_hours=new_course.discussion_hours, project_hours=new_course.project_hours, pre_requisits=new_course.pre_requisits, syllabus=new_course.syllabus, percent_quiz_1=new_course.percent_quiz_1, percent_midsem=new_course.percent_midsem, percent_quiz_2=new_course.percent_quiz_2, percent_endsem=new_course.percent_endsem, percent_project=new_course.percent_project, percent_lab_evaluation=new_course.percent_lab_evaluation, percent_course_attendance=new_course.percent_course_attendance, ref_books=new_course.ref_books) + old_course=Course.objects.filter(code=new_course.code, name=new_course.name, credit=new_course.credit, lecture_hours=new_course.lecture_hours, tutorial_hours=new_course.tutorial_hours, pratical_hours=new_course.pratical_hours, discussion_hours=new_course.discussion_hours, project_hours=new_course.project_hours, pre_requisits=new_course.pre_requisits, syllabus=new_course.syllabus, percent_quiz_1=new_course.percent_quiz_1, percent_midsem=new_course.percent_midsem, percent_quiz_2=new_course.percent_quiz_2, percent_endsem=new_course.percent_endsem, percent_project=new_course.percent_project, percent_lab_evaluation=new_course.percent_lab_evaluation, percent_course_attendance=new_course.percent_course_attendance, ref_books=new_course.ref_books,max_seats=new_course.max_seats) if old_course: # Check if disciplines or pre_requisit_courses have been changed for i in old_course: @@ -924,29 +952,32 @@ def delete_courseslot(request, courseslot_id): return render(request, 'programme_curriculum/view_a_courseslot.html', {'course_slot': courseslot}) +# views.py def add_batch_form(request): - - user_details = ExtraInfo.objects.get(user = request.user) - des = HoldsDesignation.objects.all().filter(user = request.user).first() - if request.session['currentDesignationSelected']== "student" or request.session['currentDesignationSelected']== "Associate Professor" or request.session['currentDesignationSelected']== "Professor" or request.session['currentDesignationSelected']== "Assistant Professor" : + user_details = ExtraInfo.objects.get(user=request.user) + des = HoldsDesignation.objects.all().filter(user=request.user).first() + + if request.session['currentDesignationSelected'] in ["student", "Associate Professor", "Professor", "Assistant Professor"]: return HttpResponseRedirect('/programme_curriculum/programmes/') - elif str(request.user) == "acadadmin" : + elif str(request.user) == "acadadmin": pass elif 'hod' in request.session['currentDesignationSelected'].lower(): return HttpResponseRedirect('/programme_curriculum/programmes/') - - curriculum_id = request.GET.get('curriculum_id', -1) - form = BatchForm(initial={'curriculum': curriculum_id}) - submitbutton= request.POST.get('Submit') + + # Explicitly setting curriculum to None or '' to prevent any default value + form = BatchForm(initial={'curriculum': None}) + + submitbutton = request.POST.get('Submit') if submitbutton: if request.method == 'POST': form = BatchForm(request.POST) if form.is_valid(): form.save() - messages.success(request, "Added Batch successful") + messages.success(request, "Added Batch successfully") return HttpResponseRedirect('/programme_curriculum/admin_batches/') - return render(request, 'programme_curriculum/acad_admin/add_batch_form.html',{'form':form, 'submitbutton': submitbutton}) - + + return render(request, 'programme_curriculum/acad_admin/add_batch_form.html', {'form': form, 'submitbutton': submitbutton}) + def edit_batch_form(request, batch_id): @@ -1617,4 +1648,56 @@ def file_unarchive(request,FileId): file = get_object_or_404(NewProposalFile,Q(id = FileId)) file.is_archive=False file.save() - return HttpResponseRedirect('/programme_curriculum/view_course_proposal_forms/') \ No newline at end of file + return HttpResponseRedirect('/programme_curriculum/view_course_proposal_forms/') + +@login_required(login_url='/accounts/login') +def add_course_instructor(request): + if request.session['currentDesignationSelected'] == "acadadmin": + if request.method == 'POST': + form = CourseInstructorForm(request.POST) + if form.is_valid(): + form.save() # Save the form data to the database + return redirect('/programme_curriculum/admin_instructor/') # Redirect to a success page after saving + else: + form = CourseInstructorForm() + + return render(request, 'programme_curriculum/acad_admin/add_course_instructor.html', {'form': form}) + return HttpResponseRedirect('/programme_curriculum/') + +@login_required(login_url='/accounts/login') +def admin_view_all_course_instructor(request): + if request.session.get('currentDesignationSelected') == "acadadmin": + course_instructors = CourseInstructor.objects.all() + + # Apply filtering + course_instructor_filter = CourseInstructorFilter(request.GET, queryset=course_instructors) + filtered_course_instructors = course_instructor_filter.qs + + return render(request, 'programme_curriculum/acad_admin/admin_view_all_course_instructor.html', { + 'course_instructors': filtered_course_instructors, + 'courseinstructorfilter': course_instructor_filter + }) + + return HttpResponseRedirect('/programme_curriculum/') + +@login_required(login_url='/accounts/login') +def update_course_instructor_form(request, instructor_id): + + if request.session.get('currentDesignationSelected') == "acadadmin": + # Retrieve the CourseInstructor object or return 404 if not found + course_instructor = get_object_or_404(CourseInstructor, id=instructor_id) + + if request.method == 'POST': + # Bind the form to the POST data for validation and save + form = CourseInstructorForm(request.POST, instance=course_instructor) + if form.is_valid(): + form.save() # Save the updated data to the database + return redirect('/programme_curriculum/admin_instructor/') # Redirect after successful update + else: + # Create the form with existing data (pre-populated) + form = CourseInstructorForm(instance=course_instructor) + + return render(request, 'programme_curriculum/acad_admin/add_course_instructor.html', {'form': form, 'instructor': course_instructor}) + + # Redirect to the main page if the user is not 'acadadmin' + return HttpResponseRedirect('/programme_curriculum/') \ No newline at end of file diff --git a/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241012_1459.py b/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241012_1459.py new file mode 100644 index 000000000..eebc5845e --- /dev/null +++ b/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241012_1459.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.5 on 2024-10-12 14:59 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research_procedures', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='staff_allocations', + name='start_date', + field=models.DateField(default=datetime.date.today), + ), + ] diff --git a/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241020_1126.py b/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241020_1126.py new file mode 100644 index 000000000..e94863c75 --- /dev/null +++ b/FusionIIIT/applications/research_procedures/migrations/0002_auto_20241020_1126.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.5 on 2024-10-20 11:26 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research_procedures', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='staff_allocations', + name='start_date', + field=models.DateField(default=datetime.date.today), + ), + ] diff --git a/FusionIIIT/applications/research_procedures/models.py b/FusionIIIT/applications/research_procedures/models.py index a02aad5e3..3a653e374 100644 --- a/FusionIIIT/applications/research_procedures/models.py +++ b/FusionIIIT/applications/research_procedures/models.py @@ -77,7 +77,7 @@ class staff_allocations(models.Model): year=models.IntegerField() stipend=models.IntegerField() staff_type=models.CharField(max_length=100,default="research") - start_date=models.DateField(default=datetime.date.today()) #default=datetime.date.today() + start_date=models.DateField(default=datetime.date.today) end_date=models.DateField(null=True, blank=True) def __str__(self): diff --git a/FusionIIIT/templates/academic_procedures/academicfac.html b/FusionIIIT/templates/academic_procedures/academicfac.html index 51d48685f..14e14c0b9 100644 --- a/FusionIIIT/templates/academic_procedures/academicfac.html +++ b/FusionIIIT/templates/academic_procedures/academicfac.html @@ -31,10 +31,10 @@ {% endif %} - + Submit Marks @@ -81,9 +81,9 @@

Course Version

-

Programme

+

Working Year

-

Branch

+

Semester No

Download Roll List

@@ -95,10 +95,10 @@ {% for course in assigned_courses %} {{course.course_id.name }} - {{course.course_id.code }} +
{{course.course_id.code }} {{course.course_id.version }} - {{course.batch_id.name }} - {{course.batch_id.discipline.name }} + {{course.year }} + {{course.semester_no }} @@ -120,35 +120,27 @@ -
+
diff --git a/FusionIIIT/templates/account/login.html b/FusionIIIT/templates/account/login.html index 5940456f2..b45491525 100755 --- a/FusionIIIT/templates/account/login.html +++ b/FusionIIIT/templates/account/login.html @@ -24,9 +24,9 @@
-
+
diff --git a/FusionIIIT/templates/ais/ais.html b/FusionIIIT/templates/ais/ais.html index d749b0270..b85a1e197 100755 --- a/FusionIIIT/templates/ais/ais.html +++ b/FusionIIIT/templates/ais/ais.html @@ -46,8 +46,12 @@
+ + + +{% endblock %} \ No newline at end of file diff --git a/FusionIIIT/templates/ais/start_elective_allocation.html b/FusionIIIT/templates/ais/start_elective_allocation.html new file mode 100644 index 000000000..29d48a2cf --- /dev/null +++ b/FusionIIIT/templates/ais/start_elective_allocation.html @@ -0,0 +1,181 @@ +{% load static %} +{% block start_elective_allocation %} + + +{% comment %}The tab menu starts here!{% endcomment %} + +
+
+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+

+

+
+
+ +
+
+ +
+ +
+
+
+
+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+ + + + + + + + +
students in course
+
+ +
+ +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/FusionIIIT/templates/examination/announcement_req.html b/FusionIIIT/templates/examination/announcement_req.html index 99bb51e9c..d8482bf62 100644 --- a/FusionIIIT/templates/examination/announcement_req.html +++ b/FusionIIIT/templates/examination/announcement_req.html @@ -42,9 +42,9 @@ Verify - Authenticate Course + {% comment %} Authenticate Course - + {% endcomment %} diff --git a/FusionIIIT/templates/examination/download_resultProf.html b/FusionIIIT/templates/examination/download_resultProf.html new file mode 100644 index 000000000..8c9cb2790 --- /dev/null +++ b/FusionIIIT/templates/examination/download_resultProf.html @@ -0,0 +1,139 @@ +{% extends 'examination/base.html' %} + +{% block sidetabmenu %} + +{% endblock %} +{% block content %} +

Download Result

+
+ +
+
+
+ +
+ +
+
+
+ + +
+
+ + + +
+ +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/FusionIIIT/templates/examination/generate_transcript.html b/FusionIIIT/templates/examination/generate_transcript.html index 6b1cf4a9e..89e28368d 100644 --- a/FusionIIIT/templates/examination/generate_transcript.html +++ b/FusionIIIT/templates/examination/generate_transcript.html @@ -54,9 +54,9 @@ Verify - Authenticate Course + {% comment %} Authenticate Course - + {% endcomment %} Announcement @@ -106,14 +106,9 @@

Transcript

{{ course_detail.name }} {{ course_detail.code }} {{ course_detail.credit }} - - {% if data.all_authenticators_true %} - {{ data.grade.grade }} - {% else %} - Course must be authenticated to generate grade - {% endif %} + + {{ data.grade.grade }} - {{data.all_authenticators_true}} {% endfor %} diff --git a/FusionIIIT/templates/examination/generate_transcript_form.html b/FusionIIIT/templates/examination/generate_transcript_form.html index e0bd04fae..c1ddb3619 100644 --- a/FusionIIIT/templates/examination/generate_transcript_form.html +++ b/FusionIIIT/templates/examination/generate_transcript_form.html @@ -8,9 +8,9 @@
Verify - Authenticate Course + {% comment %} Authenticate Course - + {% endcomment %} Announcement @@ -29,7 +29,7 @@

Generate Transcript Form

{% csrf_token %}
- {% for programme in programmes %} {% endfor %} @@ -38,7 +38,7 @@

Generate Transcript Form

- {% for batch in batches %} {% endfor %} @@ -47,7 +47,7 @@

Generate Transcript Form

- {% for specialization in specializations %} {% endfor %} @@ -55,7 +55,7 @@

Generate Transcript Form

- @@ -69,5 +69,62 @@

Generate Transcript Form

+ + +
-{% endblock %} + + + {% endblock %} diff --git a/FusionIIIT/templates/examination/generate_transcript_students.html b/FusionIIIT/templates/examination/generate_transcript_students.html index 6bc1edd66..f7beb443c 100644 --- a/FusionIIIT/templates/examination/generate_transcript_students.html +++ b/FusionIIIT/templates/examination/generate_transcript_students.html @@ -42,9 +42,9 @@
Verify - Authenticate Course + {% comment %} Authenticate Course - + {% endcomment %} Announcement diff --git a/FusionIIIT/templates/examination/gradeSubmission.html b/FusionIIIT/templates/examination/gradeSubmission.html index c79b41b7f..fb1109407 100644 --- a/FusionIIIT/templates/examination/gradeSubmission.html +++ b/FusionIIIT/templates/examination/gradeSubmission.html @@ -2,17 +2,12 @@ {% block sidetabmenu %}
diff --git a/FusionIIIT/templates/programme_curriculum/faculty/view_semesters_of_a_curriculum.html b/FusionIIIT/templates/programme_curriculum/faculty/view_semesters_of_a_curriculum.html index 2b8f7e5dc..78ab0b10b 100644 --- a/FusionIIIT/templates/programme_curriculum/faculty/view_semesters_of_a_curriculum.html +++ b/FusionIIIT/templates/programme_curriculum/faculty/view_semesters_of_a_curriculum.html @@ -25,7 +25,7 @@

{{ curriculum }}

Batches:     {% for batch in curriculum.batches.all %} - {{ batch }},     + {{ batch }} {% endfor %}

diff --git a/FusionIIIT/templates/programme_curriculum/view_semesters_of_a_curriculum.html b/FusionIIIT/templates/programme_curriculum/view_semesters_of_a_curriculum.html index f6e3bb580..37943388b 100644 --- a/FusionIIIT/templates/programme_curriculum/view_semesters_of_a_curriculum.html +++ b/FusionIIIT/templates/programme_curriculum/view_semesters_of_a_curriculum.html @@ -25,7 +25,7 @@

{{ curriculum }}

Batches:     {% for batch in curriculum.batches.all %} - {{ batch }},     + {{ batch }} {% endfor %}