Good Day Everyone. I met with a requirement of adding additional filtering options for Attendance Tool in School module, based on Program Filter, and I worked out the UI components pretty well.
I modified the based_on
method in student_attendance_tool.js
to the following for my requirement:
based_on: function(frm) {
if (frm.doc.based_on == "Student Batch") {
frm.set_value("course_schedule", "");
frm.set_value("program", "");
} else if (frm.doc.based_on == "Student Batch") {
frm.set_value("student_batch", "");
frm.set_value("program", "");
} else if (frm.doc.based_on == "Program Enrollment") {
frm.set_value("student_batch", "");
frm.set_value("course_schedule", "");
}
},
And then added a program
function to trigger student_batch
which in-turn calls the API method get_student_attendance_records
, so I added an additional argument program: frm.doc.program
to the args object. Everything is pretty well.
To make changes to the API call, I also made the following changes to the method:
def get_student_attendance_records(based_on, date=None, student_batch=None, course_schedule=None, program=None):
student_list = []
student_attendance_list = []
if based_on=="Course Schedule":
student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group")
if student_group:
student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "idx"] , \
filters={"parent": student_group}, order_by= "idx")
else:
student_batch = frappe.db.get_value("Course Schedule", course_schedule, "student_batch")
if not student_list and based_on == "Student Batch":
student_list = frappe.get_list("Student Batch Student", fields=["student", "student_name", "idx"] , filters={"parent": student_batch}, order_by= "idx")
if based_on == "Program Enrollment":
student_list = frappe.get_list("Program Enrollment", fields=["student", "student_name", "idx"] , filters={"program": program, "docstatus": 1}, order_by= "idx")
if course_schedule:
student_attendance_list= frappe.db.sql("""select student, status from `tabStudent Attendance` where \
course_schedule= %s and docstatus=1""", (course_schedule), as_dict=1)
elif student_batch:
student_attendance_list= frappe.db.sql("""select student, status from `tabStudent Attendance` where \
student_batch= %s and date= %s and docstatus=1 and \
(course_schedule is Null or course_schedule='')""",
(student_batch, date), as_dict=1)
elif program:
student_attendance_list= frappe.db.sql("""select student, status from `tabStudent Attendance` where \
date= %s and program= %s and docstatus=1 and \
(course_schedule is Null or course_schedule='')""",
(date, program), as_dict=1)
for attendance in student_attendance_list:
for student in student_list:
if student.student == attendance.student:
s = frappe.get_doc("Student", student.student)
student.status = attendance.status
student.student_mobile_number = s.student_mobile_number
return student_list
And this works great. Now to make the final call works well to check mandatory, I modified the validate_mandatory
method from student_attendance.py
to the following:
def validate_mandatory(self):
if not (self.student_batch or self.course_schedule or self.program):
frappe.throw(_("""Student Batch or Course Schedule or Program is mandatory"""))
And ofcourse modified the api.py
function to make changes to the mark_attendance
call while clicking the Mark Attendance
button. I just added a new arg program
and populated it - and ofcourse I modified the Attendance doctype to add the field program, so the sql above will work. Now it looks like the following:
def mark_attendance(students_present, students_absent, course_schedule=None, student_batch=None, program=None, date=None):
present = json.loads(students_present)
absent = json.loads(students_absent)
for d in present:
make_attendance_records(d["student"], d["student_name"], "Present", course_schedule, student_batch, program, date)
for d in absent:
make_attendance_records(d["student"], d["student_name"], "Absent", course_schedule, student_batch, program, date)
frappe.db.commit()
frappe.msgprint(_("Attendance has been marked successfully."))
def make_attendance_records(student, student_name, status, course_schedule=None, student_batch=None, program=None, date=None):
student_attendance = frappe.new_doc("Student Attendance")
student_attendance.student = student
student_attendance.student_name = student_name
student_attendance.course_schedule = course_schedule
student_attendance.student_batch = student_batch
student_attendance.program = program
student_attendance.date = date
student_attendance.status = status
student_attendance.submit()
To my knowledge, everything is set perfect, API is returning data properly. On the file student_attendance_tool.js
the last line of the code is $(htmls.join("")).appendTo(me.wrapper)
which is responsible for rendering the HTML into DOM, which was working well for Student Batch
and not for my custom filter, even though I didn’t miss anything I believe.
I analyzed the HTML DOM and found that it is being rendered, but the section of the HTML has hide-control
class which is not displaying the rendered grid with student data.
So I did changed the last line of the code to the following:
$(htmls.join("")).appendTo(me.wrapper).parent().parent().parent().parent().parent().parent().removeClass('hide-control');
Now everything, from API call to returning data to marking attendance works cool.
But I am not convinced with this behaviour, that made me chain .parent()
and then remove a HTML class using jQuery. I know I did a workaround, but what is the proper way to achieve this? Why was the section not rendering for my custom filter but works fine with Student Batch
filter?