fix: validate duplication

This commit is contained in:
Jannat Patel
2023-06-26 21:16:27 +05:30
parent 8353aa24f3
commit 0e1b91f1ec
8 changed files with 201 additions and 287 deletions

View File

@@ -46,7 +46,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-06-23 12:34:21.314971",
"modified": "2023-06-26 18:09:29.809564",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Assignment",
@@ -78,6 +78,7 @@
"write": 1
}
],
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],

View File

@@ -15,7 +15,9 @@ class LMSClass(Document):
def validate(self):
if self.seat_count:
self.validate_seats_left()
self.validate_duplicate_courses()
self.validate_duplicate_students()
self.validate_duplicate_assessments()
self.validate_membership()
def validate_duplicate_students(self):
@@ -28,6 +30,28 @@ class LMSClass(Document):
)
)
def validate_duplicate_courses(self):
courses = [row.course for row in self.courses]
duplicates = {course for course in courses if courses.count(course) > 1}
if len(duplicates):
title = frappe.db.get_value("LMS Course", next(iter(duplicates)), "title")
frappe.throw(
_("Course {0} has already been added to this class.").format(frappe.bold(title))
)
def validate_duplicate_assessments(self):
assessments = [row.assessment_name for row in self.assessment]
duplicates = {
assessment for assessment in assessments if assessments.count(assessment) > 1
}
if len(duplicates):
title = frappe.db.get_value("LMS Assessment", next(iter(duplicates)), "title")
frappe.throw(
_("Assessment {0} has already been added to this class.").format(
frappe.bold(next(iter(duplicates)))
)
)
def validate_membership(self):
for course in self.courses:
for student in self.students:
@@ -44,51 +68,30 @@ class LMSClass(Document):
frappe.throw(_("There are no seats available in this class."))
@frappe.whitelist()
def add_student(email, class_name):
if not frappe.db.exists("User", email):
frappe.throw(_("There is no such user. Please create a user with this Email ID."))
filters = {
"student": email,
"parent": class_name,
"parenttype": "LMS Class",
"parentfield": "students",
}
if frappe.db.exists("Class Student", filters):
frappe.throw(
_("Student {0} has already been added to this class.").format(frappe.bold(email))
)
frappe.get_doc(
{
"doctype": "Class Student",
"student": email,
"student_name": frappe.db.get_value("User", email, "full_name"),
"parent": class_name,
"parenttype": "LMS Class",
"parentfield": "students",
}
).save()
return True
@frappe.whitelist()
def remove_student(student, class_name):
frappe.only_for("Moderator")
frappe.db.delete("Class Student", {"student": student, "parent": class_name})
@frappe.whitelist()
def remove_course(course, parent):
frappe.only_for("Moderator")
frappe.db.delete("Class Course", {"course": course, "parent": parent})
@frappe.whitelist()
def remove_assessment(assessment, parent):
frappe.only_for("Moderator")
frappe.db.delete("LMS Assessment", {"assessment_name": assessment, "parent": parent})
@frappe.whitelist()
def create_live_class(
class_name, title, duration, date, time, timezone, auto_recording, description=None
):
date = format_date(date, "yyyy-mm-dd", True)
frappe.only_for("Moderator")
payload = {
"topic": title,
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
@@ -165,6 +168,7 @@ def create_class(
category=None,
name=None,
):
frappe.only_for("Moderator")
if name:
class_details = frappe.get_doc("LMS Class", name)
else:
@@ -185,26 +189,3 @@ def create_class(
)
class_details.save()
return class_details
@frappe.whitelist()
def update_assessment(type, name, value, class_name):
if not has_course_moderator_role():
return
value = cint(value)
filters = {
"assessment_type": type,
"assessment_name": name,
"parent": class_name,
"parenttype": "LMS Class",
"parentfield": "assessment",
}
exists = frappe.db.exists("LMS Assessment", filters)
if exists and not value:
frappe.db.delete("LMS Assessment", exists)
elif not exists and value:
doc = frappe.new_doc("LMS Assessment")
doc.update(filters)
doc.insert()

View File

@@ -53,4 +53,6 @@ lms.patches.v0_0.share_certificates
execute:frappe.delete_doc("Web Form", "class", ignore_missing=True, force=True)
lms.patches.v0_0.amend_course_and_lesson_editor_fields
lms.patches.v0_0.convert_course_description_to_html #11-05-2023
lms.patches.v1_0.rename_assignment_doctype
lms.patches.v1_0.rename_assignment_doctype
execute:frappe.permissions.reset_perms("LMS Assignment")
execute:frappe.permissions.reset_perms("LMS Quiz")

View File

@@ -2195,4 +2195,13 @@ select {
display: grid;
grid-template-columns: repeat(auto-fill, 150px);
grid-gap: 1rem;
}
.btn-remove-course {
opacity: 0;
margin-top: 0.25rem;
}
.btn-remove-course:hover {
opacity: 1;
}

View File

@@ -180,7 +180,15 @@
{% if class_courses | length %}
<div class="cards-parent">
{% for course in class_courses %}
{{ widgets.CourseCard(course=course, read_only=False) }}
<div>
{{ widgets.CourseCard(course=course, read_only=False) }}
<button class="btn icon-btn btn-default btn-block btn-remove-course" data-course="{{ course.name }}">
<svg class="icon icon-sm">
<use href="#icon-delete"></use>
</svg>
</button>
</div>
{% endfor %}
</div>
{% else %}
@@ -230,7 +238,7 @@
</div>
{% if is_moderator %}
<div class="col grid-static-col col-xs-1">
<svg class="icon icon-sm" style="filter: opacity(0.5)">
<svg class="icon icon-sm" style="filter: opacity(0.5)">
<use class="" href="#icon-setting-gear"></use>
</svg>
</div>
@@ -255,7 +263,7 @@
{{ student.assessments_graded }}
</div>
<div class="col grid-static-col">
{{ frappe.utils.format_datetime(student.last_active, "medium") }}
{{ frappe.utils.pretty_date(student.last_active) }}
</div>
{% if is_moderator %}
<div type="button" class="col grid-static-col col-xs-1 btn-remove-student" data-student="{{ student.student }}">
@@ -265,17 +273,7 @@
</div>
{% endif %}
</div>
<!-- <div class="progress w-25">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ student.course_completion }}"
aria-valuemin="0" aria-valuemax="100" style="width:{{ student.course_completion }}%">
<span class="sr-only"> {{ student.course_completion }} {{ _("Complete") }} </span>
</div>
</div> -->
</div>
{% endfor %}
</div>
{% else %}
@@ -297,107 +295,10 @@
</button>
{% endif %}
</header>
<!-- {{ ManageAssessments() }} -->
{{ AssessmentList(assessments) }}
</article>
{% endmacro %}
{% macro ManageAssessments() %}
{% if is_moderator %}
<div class="modal fade assessment-modal" id="assessment-modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">{{ _("Manage Assessments") }}</div>
</div>
<div class="modal-body">
<div class="mb-5">
<div class="field-label">
{{ _("Create New") }}
</div>
<p class="field-description">
{{ _("To create a new assignment or quiz for this class, click on the buttons below. Once you have created the new assignment or quiz you can come back and add it from here.") }}
</p>
<div class="flex">
<a class="btn btn-default btn-sm" href="/assignments/new-assignment">
{{ _("Create Assignment") }}
</a>
<a class="btn btn-default btn-sm ml-2" href="/quizzes/new-quiz">
{{ _("Create Quiz") }}
</a>
</div>
</div>
<form class="" id="assessment-form">
<div class="field-label mb-2">
{{ _("Select Assessments") }}
</div>
<p class="field-description">
{{ _("Select the assessments you wish to include for this class. Your selections will be automatically saved upon clicking. If you decide to remove an item from the list, simply uncheck it.") }}
</p>
<div class="">
{% if all_assignments | length %}
<div>
<div class="clickable flex align-center field-label mb-2" data-toggle="collapse" data-target="#assignments-list">
{{ _("Assignments") }}
<svg class="icon icon-sm ml-2">
<use class="mb-1" href="#icon-down"></use>
</svg>
</div>
<div id="assignments-list" class="collapse">
{% for assignment in all_assignments %}
<div>
<label class="vertically-center">
<input type="checkbox" class="assessment-item" {% if assignment.checked %} checked {% endif %} value="{{ assignment.name }}" data-type="LMS Assignment" data-name="{{ assignment.name }}">
{{ assignment.title }}
</label>
</div>
{% endfor %}
</div>
<hr>
</div>
{% endif %}
{% if all_quizzes | length %}
<div>
<div class="clickable flex align-center field-label mb-2" data-toggle="collapse" data-target="#quiz-list">
<div>
{{ _("Quizzes") }}
</div>
<svg class="icon icon-sm ml-2">
<use class="mb-1" href="#icon-down"></use>
</svg>
</div>
<div id="quiz-list" class="collapse">
{% for quiz in all_quizzes %}
<div>
<label class="vertically-center">
<input type="checkbox" class="assessment-item" {% if quiz.checked %} checked {% endif %} value="{{ quiz.name }}" data-type="LMS Quiz" data-name="{{ quiz.name }}">
{{ quiz.title }}
</label>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm btn-close" data-dismiss="modal" aria-label="Close">
{{ _("Done") }}
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% endmacro %}
{% macro AssessmentList(assessments) %}
{% if assessments | length %}
<div class="form-grid">
@@ -410,6 +311,11 @@
<div class="col grid-static-col">
{{ _("Type") }}
</div>
<div class="col grid-static-col col-xs-1">
<svg class="icon icon-sm" style="filter: opacity(0.5)">
<use href="#icon-setting-gear"></use>
</svg>
</div>
</div>
</div>
</div>
@@ -425,6 +331,11 @@
<div class="col grid-static-col">
{{ assessment.assessment_type.split("LMS ")[1] }}
</div>
<div type="button" class="col grid-static-col col-xs-1 btn-remove-assessment" data-assessment="{{ assessment.assessment_name }}">
<svg class="icon icon-sm">
<use href="#icon-delete"></use>
</svg>
</div>
</div>
</div>
{% endfor %}

View File

@@ -30,14 +30,13 @@ frappe.ready(() => {
remove_course(e);
});
$(".btn-remove-assessment").click((e) => {
remove_assessment(e);
});
$("#open-assessment-modal").click((e) => {
e.preventDefault();
show_assessment_modal();
/* $("#assessment-modal").modal("show"); */
});
$(".assessment-item").click((e) => {
update_assessment(e);
});
$(".btn-close").click((e) => {
@@ -45,54 +44,6 @@ frappe.ready(() => {
});
});
const submit_student = (e) => {
e.preventDefault();
if ($('input[data-fieldname="student_input"]').val()) {
frappe.call({
method: "lms.lms.doctype.lms_class.lms_class.add_student",
args: {
email: $('input[data-fieldname="student_input"]').val(),
class_name: $(".class-details").data("class"),
},
callback: (data) => {
frappe.show_alert(
{
message: __("Student added successfully"),
indicator: "green",
},
3
);
window.location.reload();
},
});
}
};
const remove_student = (e) => {
frappe.confirm(
"Are you sure you want to remove this student from the class?",
() => {
frappe.call({
method: "lms.lms.doctype.lms_class.lms_class.remove_student",
args: {
student: $(e.currentTarget).data("student"),
class_name: $(".class-details").data("class"),
},
callback: (data) => {
frappe.show_alert(
{
message: __("Student removed successfully"),
indicator: "green",
},
3
);
window.location.reload();
},
});
}
);
};
const create_live_class = (e) => {
let class_name = $(".class-details").data("class");
frappe.call({
@@ -353,34 +304,38 @@ const show_course_modal = () => {
],
primary_action_label: __("Add"),
primary_action(values) {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "Class Course",
course: values.course,
parenttype: "LMS Class",
parentfield: "courses",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Course Added"),
indicator: "green",
},
3
);
window.location.reload();
},
});
add_course(values);
course_modal.hide();
},
});
course_modal.show();
};
const add_course = (values) => {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "Class Course",
course: values.course,
parenttype: "LMS Class",
parentfield: "courses",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Course Added"),
indicator: "green",
},
2000
);
window.location.reload();
},
});
};
const remove_course = (e) => {
frappe.confirm("Are you sure you want to remove this course?", () => {
frappe.call({
@@ -395,7 +350,7 @@ const remove_course = (e) => {
message: __("Course Removed"),
indicator: "green",
},
3
2000
);
window.location.reload();
},
@@ -420,34 +375,63 @@ const show_student_modal = () => {
],
primary_action_label: __("Add"),
primary_action(values) {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "Class Student",
student: values.student,
parenttype: "LMS Class",
parentfield: "students",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Student Added"),
indicator: "green",
},
3
);
window.location.reload();
},
});
add_student(values);
student_modal.hide();
},
});
student_modal.show();
};
const add_student = (values) => {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "Class Student",
student: values.student,
parenttype: "LMS Class",
parentfield: "students",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Student Added"),
indicator: "green",
},
2000
);
window.location.reload();
},
});
};
const remove_student = (e) => {
frappe.confirm(
"Are you sure you want to remove this student from the class?",
() => {
frappe.call({
method: "lms.lms.doctype.lms_class.lms_class.remove_student",
args: {
student: $(e.currentTarget).data("student"),
class_name: $(".class-details").data("class"),
},
callback: (data) => {
frappe.show_alert(
{
message: __("Student removed successfully"),
indicator: "green",
},
2000
);
window.location.reload();
},
});
}
);
};
const show_assessment_modal = (e) => {
let assessment_modal = new frappe.ui.Dialog({
title: "Manage Assessments",
@@ -476,31 +460,57 @@ const show_assessment_modal = (e) => {
],
primary_action_label: __("Add"),
primary_action(values) {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "LMS Assessment",
assessment_type: values.assessment_type,
assessment_name: values.assessment_name,
parenttype: "LMS Class",
parentfield: "assessments",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Assessment Added"),
indicator: "green",
},
3
);
window.location.reload();
},
});
add_addessment(values);
assessment_modal.hide();
},
});
assessment_modal.show();
};
const add_addessment = (values) => {
frappe.call({
method: "frappe.client.insert",
args: {
doc: {
doctype: "LMS Assessment",
assessment_type: values.assessment_type,
assessment_name: values.assessment_name,
parenttype: "LMS Class",
parentfield: "assessment",
parent: $(".class-details").data("class"),
},
},
callback(r) {
frappe.show_alert(
{
message: __("Assessment Added"),
indicator: "green",
},
2000
);
window.location.reload();
},
});
};
const remove_assessment = (e) => {
frappe.confirm("Are you sure you want to remove this assessment?", () => {
frappe.call({
method: "lms.lms.doctype.lms_class.lms_class.remove_assessment",
args: {
assessment: $(e.currentTarget).data("assessment"),
parent: $(".class-details").data("class"),
},
callback(r) {
frappe.show_alert(
{
message: __("Assessment Removed"),
indicator: "green",
},
2000
);
window.location.reload();
},
});
});
};

View File

@@ -48,7 +48,7 @@ def get_context(context):
class_students = frappe.get_all(
"Class Student",
{"parent": class_name},
["student", "student_name", "username"],
["name", "student", "student_name", "username"],
order_by="creation desc",
)

View File