From 5b03a4b307fb5cc868fc637b59bda553c17d20c6 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Tue, 22 Apr 2025 04:48:16 +0545 Subject: [PATCH] signals(fix): huge errors --- app/management/commands/generate_fake_data.py | 339 +++++++++++------- app/signals.py | 134 ++++--- 2 files changed, 303 insertions(+), 170 deletions(-) diff --git a/app/management/commands/generate_fake_data.py b/app/management/commands/generate_fake_data.py index 4ba4b79..b9f1ea3 100644 --- a/app/management/commands/generate_fake_data.py +++ b/app/management/commands/generate_fake_data.py @@ -14,6 +14,8 @@ from app.models import ( CourseTracking, TeacherParentMeeting, SubjectFile ) +fake = Faker() + # Nepali-specific data NEPALI_FIRST_NAMES = [ 'Aarav', 'Aayush', 'Abhishek', 'Aditya', 'Amit', 'Anish', 'Anup', 'Arjun', 'Ashish', 'Bibek', @@ -70,6 +72,8 @@ class Command(BaseCommand): # Get or create groups staff_group, _ = Group.objects.get_or_create(name='Teacher') + hod_group, _ = Group.objects.get_or_create(name='HOD') + admission_officer_group, _ = Group.objects.get_or_create(name='Admission Officer') parent_group, _ = Group.objects.get_or_create(name='Parent') student_group, _ = Group.objects.get_or_create(name='Student') @@ -121,7 +125,7 @@ class Command(BaseCommand): subjects = self._create_subjects(fake, courses, subjects_per_course) # Create staff members - staff_members = self._create_staff(fake, num_staff, staff_group) + staff_members = self._create_staff(fake, num_staff, staff_group, courses) # Create students students = self._create_students(fake, num_students, courses, batches, student_group) @@ -159,7 +163,24 @@ class Command(BaseCommand): return f"{random.choice(NEPALI_FIRST_NAMES)} {random.choice(NEPALI_LAST_NAMES)}" def _get_nepali_phone(self): - return f"98{random.randint(1000000, 9999999)}" + """Generate a realistic Nepali phone number with proper formatting""" + # Nepal mobile prefixes (2 digits) + prefixes = ['98', '97'] + + # Generate a unique phone number + while True: + prefix = random.choice(prefixes) + # Generate 8 digits after the prefix (no spaces, no formatting) + digits = ''.join([str(random.randint(0, 9)) for _ in range(8)]) + phone = f"{prefix}{digits}" + + # Check if this phone already exists in any user model + student_exists = Student.objects.filter(phone=phone).exists() + staff_exists = Staff.objects.filter(phone=phone).exists() + parent_exists = Parent.objects.filter(phone=phone).exists() + + if not (student_exists or staff_exists or parent_exists): + return phone def _get_nepali_address(self, city): wards = [f"Ward No. {random.randint(1, 35)}" for _ in range(3)] @@ -233,7 +254,7 @@ class Command(BaseCommand): return courses def _create_subjects(self, fake, courses, subjects_per_course): - subjects = [] + subjects = [] # Initialize subjects list subject_names = { 'Computer Science Engineering': [ @@ -285,7 +306,7 @@ class Command(BaseCommand): for year_or_sem in range(1, max_year_or_sem + 1): # Calculate number of subjects for this year/semester # Ensure at least 2 subjects per year/semester - num_subjects = max(2, min(subjects_per_course, len(course_subjects))) + num_subjects = max(2, min(subjects_per_course, len(course_subject_names))) # Select subjects ensuring no duplicates within the same year/semester available_subjects = [s for s in course_subject_names if not Subject.objects.filter( @@ -319,36 +340,72 @@ class Command(BaseCommand): return subjects - def _create_staff(self, fake, num_staff, staff_group): + def _create_staff(self, fake, num_staff, staff_group, courses): staff_members = [] + # Keep track of which courses already have an HOD + courses_with_hod = set() for i in range(num_staff): - gender = random.choice(['Male', 'Female']) - name = self._get_nepali_name() - phone = self._get_nepali_phone() - city = random.choice(NEPALI_CITIES) - - staff = Staff( - name=name, - phone=phone, - designation=random.choice(NEPALI_DESIGNATIONS), - gender=gender, - birth_date=fake.date_of_birth(minimum_age=30, maximum_age=65), - email=fake.email(), - temporary_address=self._get_nepali_address(city), - permanent_address=self._get_nepali_address(city), - marital_status=random.choice(['Married', 'Unmarried']), - parent_name=self._get_nepali_name(), - parent_phone=self._get_nepali_phone(), - citizenship_no=f"{random.randint(10000, 99999)}-{random.randint(10000, 99999)}", - joining_date=fake.date_between(start_date='-5y', end_date='today') - ) - staff.save() - staff.set_password('staff123') - staff.save() - staff.groups.add(staff_group) - staff_members.append(staff) - self.stdout.write(self.style.SUCCESS(f'Created staff: {staff.name}')) + try: + gender = random.choice(['Male', 'Female']) + name = self._get_nepali_name() + phone = self._get_nepali_phone() + city = random.choice(NEPALI_CITIES) + designation = random.choice(NEPALI_DESIGNATIONS) + + staff = Staff( + name=name, + phone=phone, + designation=designation, + gender=gender, + birth_date=fake.date_of_birth(minimum_age=30, maximum_age=65), + email=fake.email(), + temporary_address=self._get_nepali_address(city), + permanent_address=self._get_nepali_address(city), + marital_status=random.choice(['Married', 'Unmarried']), + parent_name=self._get_nepali_name(), + parent_phone=self._get_nepali_phone(), + citizenship_no=f"{random.randint(10000, 99999)}-{random.randint(10000, 99999)}", + joining_date=fake.date_between(start_date='-5y', end_date='today') + ) + + # First save to get a primary key + staff.save() + + # Set appropriate password based on designation + password = 'staff123' + if staff.designation == 'HOD': + password = 'hod1234' + elif staff.designation == 'Admission Officer': + password = 'admissionofficer123' + + staff.set_password(password) + + # Add staff to the appropriate group based on designation + if staff.designation == 'HOD': + hod_group = Group.objects.get(name='HOD') + staff.groups.add(hod_group) + + # Assign a course to HOD that doesn't already have one + available_courses = [c for c in courses if c.id not in courses_with_hod] + if available_courses: + course = random.choice(available_courses) + staff.course = course + courses_with_hod.add(course.id) + staff.save() + self.stdout.write(self.style.SUCCESS(f'Assigned HOD {staff.name} to course: {course.name}')) + + elif staff.designation == 'Admission Officer': + admission_officer_group = Group.objects.get(name='Admission Officer') + staff.groups.add(admission_officer_group) + else: + staff.groups.add(staff_group) + + staff_members.append(staff) + self.stdout.write(self.style.SUCCESS(f'Created staff: {staff.name} with phone {phone} (password: {password})')) + except Exception as e: + self.stdout.write(self.style.ERROR(f'Error creating staff: {str(e)}')) + continue return staff_members @@ -356,45 +413,61 @@ class Command(BaseCommand): students = [] for i in range(num_students): - gender = random.choice(['Male', 'Female']) - name = self._get_nepali_name() - phone = self._get_nepali_phone() - city = random.choice(NEPALI_CITIES) - - course = random.choice(courses) - max_semester = course.duration - if course.duration_type == 'Semester': - max_semester = course.duration * 2 - - current_period = random.randint(1, max_semester) - - student = Student( - name=name, - status=random.choice(['Active', 'Leave', 'Completed']), - gender=gender, - birth_date=fake.date_of_birth(minimum_age=18, maximum_age=25), - email=fake.email(), - phone=phone, - temporary_address=self._get_nepali_address(city), - permanent_address=self._get_nepali_address(city), - marital_status=random.choice(['Married', 'Unmarried']), - parent_name=self._get_nepali_name(), - parent_phone=self._get_nepali_phone(), - citizenship_no=f"{random.randint(10000, 99999)}-{random.randint(10000, 99999)}", - course=course, - current_period=current_period, - joining_date=fake.date_between(start_date='-4y', end_date='-1y') - ) - student.save() - student.set_password('student123') - student.save() - student.groups.add(student_group) - - batch = random.choice(batches) - student.batches.add(batch) - - students.append(student) - self.stdout.write(self.style.SUCCESS(f'Created student: {student.name}')) + try: + # Generate random personal information + name = self._get_nepali_name() + email = fake.email() + phone = self._get_nepali_phone() + city = random.choice(NEPALI_CITIES) + temp_address = self._get_nepali_address(city) + perm_address = self._get_nepali_address(city) + birth_date = fake.date_of_birth(minimum_age=18, maximum_age=25) + + # Generate random academic information + course = random.choice(courses) + batch = random.choice(batches) + current_period = random.randint(1, course.duration) + if course.duration_type == 'Semester': + current_period = random.randint(1, course.duration * 2) + + # Generate random joining date (within last 2 years) + joining_date = datetime.now().date() - timedelta(days=random.randint(0, 730)) + + # Create student + student = Student( + name=name, + email=email, + phone=phone, + temporary_address=temp_address, + permanent_address=perm_address, + birth_date=birth_date, + course=course, + current_period=current_period, + joining_date=joining_date, + gender=random.choice(['Male', 'Female', 'Other']), + status='Active', + is_active=True + ) + + # Save the student first to get a primary key + student.save() + + # Set password and save again + password = 'student123' + student.set_password(password) + + # Add student to batch + batch.students.add(student) + + # Add to student group + student.groups.add(student_group) + + students.append(student) + self.stdout.write(self.style.SUCCESS(f'Created student {student.name} with phone {phone} (password: {password})')) + except Exception as e: + # Log the error but continue with other students + self.stdout.write(self.style.ERROR(f'Error creating student: {str(e)}')) + continue # Continue with next student return students @@ -402,28 +475,44 @@ class Command(BaseCommand): parents = [] for i in range(num_parents): - name = self._get_nepali_name() - phone = self._get_nepali_phone() - city = random.choice(NEPALI_CITIES) - - parent = Parent( - name=name, - phone=phone, - email=fake.email(), - address=self._get_nepali_address(city), - ) - parent.save() - parent.set_password('parent123') - parent.save() - parent.groups.add(parent_group) - - num_children = random.randint(1, min(2, len(students))) - assigned_students = random.sample(students, num_children) - for student in assigned_students: - parent.students.add(student) - - parents.append(parent) - self.stdout.write(self.style.SUCCESS(f'Created parent: {parent.name} with {num_children} children')) + try: + name = self._get_nepali_name() + phone = self._get_nepali_phone() + city = random.choice(NEPALI_CITIES) + + parent = Parent( + name=name, + phone=phone, + email=fake.email(), + address=self._get_nepali_address(city), + ) + + # First save to get a primary key + parent.save() + + # Set password directly using Django's method + password = 'parent123' + parent.set_password(password) + + # Add parent to group + parent.groups.add(parent_group) + + # Assign students to parent if available + if students: + num_children = min(2, len(students)) + if num_children > 0: + assigned_students = random.sample(students, num_children) + for student in assigned_students: + parent.students.add(student) + + self.stdout.write(self.style.SUCCESS(f'Created parent: {parent.name} with phone {phone} (password: {password}) with {num_children} children')) + else: + self.stdout.write(self.style.SUCCESS(f'Created parent: {parent.name} with phone {phone} (password: {password}) with no children')) + + parents.append(parent) + except Exception as e: + self.stdout.write(self.style.ERROR(f'Error creating parent: {str(e)}')) + continue return parents @@ -853,39 +942,41 @@ class Command(BaseCommand): # Calculate dates based on student's joining date joining_date = student.joining_date or datetime.now().date() - timedelta(days=random.randint(180, 365)) - expected_end_date = joining_date + timedelta(days=365 * course.duration) - # Calculate completion percentage based on current semester - if course.duration_type == 'Semester': - total_semesters = course.duration * 2 - completion_percentage = int((student.current_period / total_semesters) * 100) + # Calculate expected end date based on course duration type + if course.duration_type == "Year": + years = course.duration + duration_days = years * 365 + else: # Semester based + years = course.duration + duration_days = years * 365 # Use full years for total duration + + expected_end_date = joining_date + timedelta(days=duration_days) + + # Set period dates based on duration type + period_start_date = joining_date + if course.duration_type == "Semester": + period_end_date = joining_date + timedelta(days=180) # 6 months else: - completion_percentage = int((student.current_period / course.duration) * 100) + period_end_date = joining_date + timedelta(days=365) # 1 year - # Determine progress status - if completion_percentage == 100: - progress_status = 'Completed' - elif completion_percentage == 0: - progress_status = 'Not Started' - else: - progress_status = 'In Progress' - - tracking = CourseTracking( - student=student, - course=course, - enrollment_date=joining_date, - start_date=joining_date, - expected_end_date=expected_end_date, - actual_end_date=None, - progress_status=progress_status, - completion_percentage=completion_percentage, - current_period=student.current_period, - semester_start_date=joining_date, - notes=f"Tracking student progress in {course.name}" - ) - tracking.save() - self.stdout.write(self.style.SUCCESS(f'Created course tracking for {student.name} in {course.name}')) + # Create course tracking - save() will automatically update completion percentage + with transaction.atomic(): + course_tracking = CourseTracking.objects.create( + student=student, + course=course, + enrollment_date=joining_date, + start_date=joining_date, + expected_end_date=expected_end_date, + current_period=student.current_period, + period_start_date=period_start_date, + period_end_date=period_end_date, + progress_status="In Progress", + notes=fake.paragraph() if random.random() < 0.3 else None + ) + + self.stdout.write(self.style.SUCCESS(f'Created course tracking for student: {student.name} in course: {course.name}')) + except Exception as e: - # Log the error but continue with other students - self.stdout.write(self.style.ERROR(f'Error creating course tracking for {student.name}: {str(e)}')) - continue # Continue with next student + self.stdout.write(self.style.ERROR(f'Error creating course tracking for student {student.name}: {str(e)}')) + continue diff --git a/app/signals.py b/app/signals.py index e31e186..8142477 100644 --- a/app/signals.py +++ b/app/signals.py @@ -29,6 +29,13 @@ from app.models import ( TOTPSecret, ResetToken, OTPAttempt, + AttendanceRecord, + StudentLeave, + Attendance, + ParentFeedback, + ParentInstituteFeedback, + Routine, + SubjectFile, ) from app.utils import cleanup_expired_tokens @@ -479,15 +486,22 @@ def cleanup_orphaned_student_data(sender, instance, **kwargs): Clean up orphaned data when a student is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted student: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"student_{instance.id}_*") - if instance.parent: - cache.delete_pattern(f"parent_{instance.parent.id}_*") + # Handle cleanup safely without relying on the model's method + # Delete attendance records + AttendanceRecord.objects.filter(student=instance).delete() + + # Delete course tracking + CourseTracking.objects.filter(student=instance).delete() + + # Delete leave requests + StudentLeave.objects.filter(student=instance).delete() + + # Clear any cached data + cache.delete(f"student_{instance.id}_profile") + cache.delete(f"student_{instance.id}_progress") + cache.delete(f"student_{instance.id}_attendance") + + logger.info(f"Cleaned up orphaned data for deleted student: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned student data: {str(e)}") @@ -497,13 +511,18 @@ def cleanup_orphaned_staff_data(sender, instance, **kwargs): Clean up orphaned data when a staff member is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted staff: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"staff_{instance.id}_*") + # Handle cleanup safely without relying on the model's method + # Delete attendance records where this staff is the teacher + Attendance.objects.filter(teacher=instance).delete() + + # Clear course relationships + instance.courses_taught.clear() + + # Clear any cached data + cache.delete(f"staff_{instance.id}_profile") + cache.delete(f"staff_{instance.id}_schedule") + + logger.info(f"Cleaned up orphaned data for deleted staff: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned staff data: {str(e)}") @@ -513,15 +532,15 @@ def cleanup_orphaned_course_tracking_data(sender, instance, **kwargs): Clean up orphaned data when a course tracking record is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted course tracking: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"course_tracking_{instance.id}_*") - if instance.student: - cache.delete_pattern(f"student_{instance.student.id}_*") + # No need for transaction, just clear cache + # CourseTracking doesn't have its own cleanup_orphaned_data method + + # Clear any cached data - use individual deletions instead of patterns + cache.delete(f"course_tracking_{instance.id}_percentage") + if instance.student: + cache.delete(f"student_{instance.student.id}_progress") + + logger.info(f"Cleaned up cached data for deleted course tracking: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned course tracking data: {str(e)}") @@ -531,13 +550,18 @@ def cleanup_orphaned_parent_data(sender, instance, **kwargs): Clean up orphaned data when a parent is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted parent: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"parent_{instance.id}_*") + # Handle cleanup safely without relying on the model's method + # Delete feedback records + ParentFeedback.objects.filter(parent=instance).delete() + + # Delete institute feedback + ParentInstituteFeedback.objects.filter(parent=instance).delete() + + # Clear any cached data + cache.delete(f"parent_{instance.id}_profile") + cache.delete(f"parent_{instance.id}_students") + + logger.info(f"Cleaned up orphaned data for deleted parent: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned parent data: {str(e)}") @@ -547,13 +571,21 @@ def cleanup_orphaned_course_data(sender, instance, **kwargs): Clean up orphaned data when a course is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted course: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"course_{instance.id}_*") + # Handle cleanup safely without relying on the model's method + # Delete subjects + Subject.objects.filter(course=instance).delete() + + # Delete course tracking records + CourseTracking.objects.filter(course=instance).delete() + + # Delete routines + Routine.objects.filter(course=instance).delete() + + # Clear any cached data + cache.delete(f"course_{instance.id}_details") + cache.delete(f"course_{instance.id}_subjects") + + logger.info(f"Cleaned up orphaned data for deleted course: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned course data: {str(e)}") @@ -563,13 +595,23 @@ def cleanup_orphaned_subject_data(sender, instance, **kwargs): Clean up orphaned data when a subject is deleted """ try: - with transaction.atomic(): - # Clean up related records - instance.cleanup_orphaned_data() - logger.info(f"Cleaned up orphaned data for deleted subject: {instance.id}") - - # Clear any cached data - cache.delete_pattern(f"subject_{instance.id}_*") + # Handle cleanup safely without relying on the model's method + # Delete subject files + SubjectFile.objects.filter(subject=instance).delete() + + # Delete routines + Routine.objects.filter(subject=instance).delete() + + # Delete attendance records + attendances = Attendance.objects.filter(routine__subject=instance) + for attendance in attendances: + attendance.delete() + + # Clear any cached data + cache.delete(f"subject_{instance.id}_details") + cache.delete(f"subject_{instance.id}_files") + + logger.info(f"Cleaned up orphaned data for deleted subject: {instance.id}") except Exception as e: logger.error(f"Error cleaning up orphaned subject data: {str(e)}")