refactor: renamed app to school

This commit is contained in:
Jannat Patel
2021-10-11 20:31:27 +05:30
parent 13022e0bcc
commit d07dbcc50a
466 changed files with 2497 additions and 146 deletions

0
school/www/__init__.py Normal file
View File

View File

View File

View File

View File

@@ -0,0 +1,72 @@
% extends "templates/base.html" %}
{% block title %}Join a Course{% endblock %}
{% block head_include %}
<meta name="description" content="Join a Course"/>
<meta name="keywords" content="" />
{% endblock %}
{% block content %}
{% if frappe.session.user == "Guest" %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Login Required</span>
</div>
<div class=''>Please log in to confirm joining the course {{ batch.course_title }}.</div>
<a type="submit" id="login" class="btn btn-primary w-100"
href="/login?redirect-to=/courses/{{ batch.course }}/join?batch={{ batch.name }}">{{_("Login")}}</a>
</div>
{% elif already_a_member %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Already a member</span>
</div>
<div class=''>You are already a member of the batch {{ batch.title }} for the course {{ batch.course_title }}.
</div>
<a type="submit" id="batch-home" class="btn btn-primary w-100" href="">{{_("Go to Batch Home")}}</a>
</div>
{% else %}
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue password-box'>Confirm your membership</span>
</div>
<div>Please provide your confirmation to be a part of the batch {{ batch.title }} for the course
{{ batch.course_title }}.
</div>
<a type="submit" id="confirm" class="btn btn-primary w-100">{{_("Confirm")}}</a>
</div>
{% endif %}
{% endblock %}
{% block script %}
<script>
frappe.ready(() => {
$("#confirm").click((e) => {
frappe.call({
"method": "school.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": {{ batch.name }},
"course": {{ batch.course }}
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint({
message: __("You are now a member of this batch!"),
clear: true
});
setTimeout(function () {
window.location.href = "/courses/{{ batch.course }}/home";
}, 2000);
}
}
})
})
})
</script>
{% endblock %}

8
school/www/batch/join.py Normal file
View File

@@ -0,0 +1,8 @@
import frappe
def get_context(context):
context.no_cache = 1
batch_name = frappe.form_dict["batch"]
context.batch = frappe.get_doc("LMS Batch", batch_name)
context.already_a_member = context.batch.is_member(frappe.session.user)
context.batch.course_title = frappe.db.get_value("LMS Course", context.batch.course, "title")

133
school/www/batch/learn.html Normal file
View File

@@ -0,0 +1,133 @@
{% extends "templates/base.html" %}
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
{% block title %} {{ lesson.title }} - {{ course.title }} {% endblock %}
{% block head_include %}
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
{% for ext in page_extensions %}
{{ ext.render_header() }}
{% endfor %}
{% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container course-details-page">
{{ widgets.BreadCrumb(course=course, lesson=lesson) }}
<div class="course-content-parent">
<div class="course-details-outline">
{{ widgets.CourseOutline(course=course, membership=membership) }}
</div>
<div class="lesson-pagination-parent">
{{ LessonContent(lesson) }}
{% if membership %}
{{ pagination(prev_url, next_url) }}
{% endif %}
</div>
</div>
{{ Discussions() }}
</div>
</div>
{% endblock %}
{% macro LessonContent(lesson) %}
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lesson-content">
<div class="course-home-headings title
{% if membership %} is-member {% endif %}
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
data-course="{{ course.name }}">
{{ lesson.title }}
<span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span>
</div>
{% if membership or lesson.include_in_preview or is_instructor %}
<div class="common-card-style lesson-content-card markdown-source">
{% if is_instructor and not lesson.include_in_preview %}
<small class="alert alert-secondary alert-dismissible">
This lesson is not available for preview. As you are the Instructor of the course only you can see it.
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
</small>
{% endif %}
{{ lesson.render_html() }}</div>
{% else %}
<div class="common-card-style lesson-content-card">
<div class="w-25 text-center" style="margin: 0 auto;">
<small>This lesson is not available for preview. Please join the course to access it.</small>
<a class="button is-primary ml-auto mr-auto mt-3" href="/courses/{{ course.name }}"> Start Learning </a>
</div>
</div>
{% endif %}
</div>
{% endmacro %}
{% macro pagination(prev_url, next_url) %}
<div class="lesson-pagination">
<div>
{% if prev_url %}
<a class="button is-secondary dark-links prev" href="{{ prev_url }}">
<img class="mr-2" src="/assets/school/icons/left-arrow.svg">
Prev
</a>
{% endif %}
</div>
{% if not course.is_mentor(frappe.session.user) and membership %}
{% if course.get_progress(lesson.name) != "Complete" %}
<div class="button is-secondary" id="progress" data-progress="Complete">
Mark as Complete
</div>
{% else %}
<div class="button is-secondary" id="progress" data-progress="Incomplete">
Mark as Incomplete
</div>
{% endif %}
{% endif %}
<div>
{% if next_url %}
<a class="button is-primary next" href="{{ next_url }}">
Next
<img class="ml-2" src="/assets/school/icons/side-arrow-white.svg">
</a>
{% elif course.enable_certification %}
<div class="button is-primary {% if course.get_course_progress() != 100 %} hide {% endif %}" id="certification">
Get Certificate
</div>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro Discussions() %}
{% set is_instructor = frappe.session.user == course.instructor %}
{% set condition = is_instructor if is_instructor else membership %}
{% set doctype, docname = "Course Lesson", lesson.name %}
{% set title = "Questions" %}
{% set cta_title = "New Question" %}
{% set button_name = "Start Learning" %}
{% set redirect_to = "/courses/" + course.name %}
{% include "frappe/templates/discussions/discussions_section.html" %}
{% endmacro %}
{%- block script %}
{{ super() }}
<script type="text/javascript">
var page_context = {{ page_context | tojson }};
</script>
{% for ext in page_extensions %}
{{ ext.render_footer() }}
{% endfor %}
{%- endblock %}

199
school/www/batch/learn.js Normal file
View File

@@ -0,0 +1,199 @@
frappe.ready(() => {
localStorage.removeItem($("#quiz-title").text());
save_current_lesson();
$(".option").click((e) => {
enable_check(e);
})
$("#progress").click((e) => {
mark_progress(e);
});
$("#summary").click((e) => {
quiz_summary(e);
});
$("#check").click((e) => {
check_answer(e);
});
$("#next").click((e) => {
mark_active_question(e);
});
$("#try-again").click((e) => {
try_quiz_again(e);
});
$("#certification").click((e) => {
create_certificate(e);
})
})
var save_current_lesson = () => {
if ($(".title").hasClass("is-member")) {
frappe.call("school.lms.api.save_current_lesson", {
course_name: $(".title").attr("data-course"),
lesson_name: $(".title").attr("data-lesson")
})
}
}
var enable_check = (e) => {
if ($(".option:checked").length && $("#check").attr("disabled")) {
$("#check").removeAttr("disabled");
}
}
var mark_active_question = (e = undefined) => {
var current_index;
var next_index = 1;
if (e) {
e.preventDefault();
current_index = $(".active-question").attr("data-qt-index");
next_index = parseInt(current_index) + 1;
}
$(".question").addClass("hide").removeClass("active-question");
$(`.question[data-qt-index='${next_index}']`).removeClass("hide").addClass("active-question");
$(".current-question").text(`${next_index}`);
$("#check").removeClass("hide").attr("disabled", true);
$("#next").addClass("hide");
$(".explanation").addClass("hide");
}
var mark_progress = (e) => {
var status = $(e.currentTarget).attr("data-progress");
frappe.call({
method: "school.lms.doctype.course_lesson.course_lesson.save_progress",
args: {
lesson: $(".title").attr("data-lesson"),
course: $(".title").attr("data-course"),
status: status
},
callback: (data) => {
change_progress_indicators(status, e);
if (data.message == 100 && !$(".next").length && $("#certification").hasClass("hide")) {
$("#certification").removeClass("hide");
}
}
})
}
var change_progress_indicators = (status, e) => {
if (status == "Complete") {
$(".lesson-progress").removeClass("hide");
$(".active-lesson .lesson-progress-tick").removeClass("hide");
}
else {
$(".lesson-progress").addClass("hide");
$(".active-lesson .lesson-progress-tick").addClass("hide");
}
var label = status != "Complete" ? "Mark as Complete" : "Mark as Incomplete";
var data_progress = status != "Complete" ? "Complete" : "Incomplete";
$(e.currentTarget).text(label).attr("data-progress", data_progress);
}
var quiz_summary = (e) => {
e.preventDefault();
var quiz_name = $("#quiz-title").text();
var total_questions = $(".question").length;
frappe.call({
method: "school.lms.doctype.lms_quiz.lms_quiz.quiz_summary",
args: {
"quiz": quiz_name,
"results": localStorage.getItem(quiz_name)
},
callback: (data) => {
var message = data.message == total_questions ? "Excellent Work" : "You were almost there."
$(".question").addClass("hide");
$(".quiz-footer").addClass("hide");
$("#quiz-form").parent().prepend(
`<div class="text-center summary"><h2>${message} 👏 </h2>
<div class="font-weight-bold">${data.message}/${total_questions} correct.</div></div>`);
$("#try-again").removeClass("hide");
}
})
}
var try_quiz_again = (e) => {
window.location.reload();
}
var check_answer = (e) => {
e.preventDefault();
var quiz_name = $("#quiz-title").text();
var total_questions = $(".question").length;
var current_index = $(".active-question").attr("data-qt-index");
$(".explanation").removeClass("hide");
$("#check").addClass("hide");
if (current_index == total_questions) {
if ($(".eligible-for-submission").length) {
$("#summary").removeClass("hide")
}
else {
$("#submission-message").removeClass("hide");
}
}
else {
$("#next").removeClass("hide")
}
var [answer, is_correct] = parse_options();
add_to_local_storage(quiz_name, current_index, answer, is_correct)
}
var parse_options = () => {
var answer = [];
var is_correct = [];
$(".active-question input").each((i, element) => {
var correct = parseInt($(element).attr("data-correct"));
if ($(element).prop("checked")) {
answer.push(decodeURIComponent($(element).val()));
correct && is_correct.push(1);
correct ? add_icon(element, "check") : add_icon(element, "wrong");
}
else {
correct && is_correct.push(0);
correct ? add_icon(element, "minus-circle-green") : add_icon(element, "minus-circle");
}
})
return [answer, is_correct];
}
var add_icon = (element, icon) => {
var label = $(element).parent().find(".label-area p").text();
$(element).parent().empty().html(`<img class="mr-3" src="/assets/school/icons/${icon}.svg"> ${label}`);
}
var add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
var quiz_stored = JSON.parse(localStorage.getItem(quiz_name));
var quiz_obj = {
"question_index": current_index,
"answer": answer.join(),
"is_correct": is_correct
}
quiz_stored ? quiz_stored.push(quiz_obj) : quiz_stored = [quiz_obj]
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
}
var create_certificate = (e) => {
e.preventDefault();
course = $(".title").attr("data-course");
frappe.call({
method: "school.lms.doctype.lms_certification.lms_certification.create_certificate",
args: {
"course": course
},
callback: (data) => {
window.location.href = `/courses/${course}/${data.message}`;
}
})
}

59
school/www/batch/learn.py Normal file
View File

@@ -0,0 +1,59 @@
from re import I
import frappe
from . import utils
from frappe.utils import cstr
from school.www import batch
def get_context(context):
utils.get_common_context(context)
chapter_index = frappe.form_dict.get("chapter")
lesson_index = frappe.form_dict.get("lesson")
lesson_number = f"{chapter_index}.{lesson_index}"
if not chapter_index or not lesson_index:
if context.batch:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
else:
index_ = "1.1"
utils.redirect_to_lesson(context.course, index_)
context.lesson = get_current_lesson_details(lesson_number, context)
neighbours = context.course.get_neighbours(lesson_number, context.lessons)
context.next_url = get_learn_url(neighbours["next"], context.course)
context.prev_url = get_learn_url(neighbours["prev"], context.course)
meta_info = context.lesson.title + " - " + context.course.title
context.metatags = {
"title": meta_info,
"keywords": meta_info,
"description": meta_info
}
context.page_extensions = get_page_extensions()
context.page_context = {
"course": context.course.name,
"batch": context.get("batch") and context.batch.name,
"lesson": context.lesson.name,
"is_member": context.membership is not None
}
def get_current_lesson_details(lesson_number, context):
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
if not len(details_list):
utils.redirect_to_lesson(context.course)
return details_list[0]
def get_learn_url(lesson_number, course):
return course.get_learn_url(lesson_number) and course.get_learn_url(lesson_number) + course.query_parameter
def get_lesson_index(course, batch, user):
lesson = batch.get_current_lesson(user)
return lesson and course.get_lesson_index(lesson)
def get_page_extensions():
default_value = ["school.plugins.PageExtension"]
classnames = frappe.get_hooks("school_lesson_page_extensions") or default_value
extensions = [frappe.get_attr(name)() for name in classnames]
return extensions

35
school/www/batch/utils.py Normal file
View File

@@ -0,0 +1,35 @@
import frappe
from school.lms.models import Course
def get_common_context(context):
context.no_cache = 1
course_name = frappe.form_dict["course"]
try:
batch_name = frappe.form_dict["batch"]
except KeyError:
batch_name = None
course = frappe.get_doc("LMS Course", course_name)
if not course:
context.template = "www/404.html"
return
context.course = course
context.lessons = course.get_lessons()
membership = course.get_membership(frappe.session.user, batch_name)
context.membership = membership
if membership:
batch = course.get_batch(membership.batch)
if batch:
context.batch = batch
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.livecode_url = get_livecode_url()
def get_livecode_url():
return frappe.db.get_single_value("LMS Settings", "livecode_url")
def redirect_to_lesson(course, index_="1.1"):
frappe.local.flags.redirect_location = course.get_learn_url(index_) + course.query_parameter
raise frappe.Redirect

View File

View File

@@ -0,0 +1,25 @@
{% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import MentorsSection %}
{% block title %} {{ student.full_name }} - {{ course.title }} {% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container certificate-page">
<div class="breadcrumb">
<a class="dark-links" href="/courses">All Courses</a>
<img class="ml-1 mr-1" src="/assets/school/icons/chevron-right.svg">
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
</div>
{% if certificate.student == frappe.session.user %}
<div class="comment-footer mb-5">
<div class="button is-secondary pull-right" id="export-as-pdf" data-certificate="{{ certificate.name }}"
data-certificate-name="{{ student.full_name }} - {{ course.title }}">Export</div>
</div>
{% endif %}
{% include "school/templates/certificate.html" %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,25 @@
frappe.ready(() => {
$("#export-as-pdf").click((e) => {
export_as_pdf(e);
})
})
var export_as_pdf = (e) => {
var button = $(e.currentTarget);
button.text(__("Exporting..."));
html2canvas(document.querySelector('.common-card-style'), {
scrollY: -window.scrollY,
scrollX: 0
}).then(function(canvas) {
let dataURL = canvas.toDataURL('image/png');
let a = document.createElement('a');
a.href = dataURL;
a.download = button.attr("data-certificate-name");
a.click();
}).finally(() => {
button.text(__("Export"))
});
}

View File

@@ -0,0 +1,31 @@
import frappe
def get_context(context):
context.no_cache = 1
try:
course_name = frappe.form_dict["course"]
certificate_name = frappe.form_dict["certificate"]
except KeyError:
redirect_to_course_list()
context.certificate = frappe.db.get_value("LMS Certification", certificate_name,
["name", "student", "issue_date", "expiry_date", "course"], as_dict=True)
if context.certificate.course != course_name:
redirect_to_course_list()
context.course = frappe.db.get_value("LMS Course", course_name,
["instructor", "title", "name"], as_dict=True)
context.instructor = frappe.db.get_value("User", context.course.instructor,
["full_name", "username"], as_dict=True)
context.student = frappe.db.get_value("User", context.certificate.student,
["full_name"], as_dict=True)
context.logo = frappe.db.get_single_value("Website Settings", "banner_image")
def redirect_to_course_list():
frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect

View File

@@ -0,0 +1,211 @@
{% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import MentorsSection %}
{% block title %}{{ course.title }}
{% endblock %}
{% block head_include %}
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container course-home-page">
{{ widgets.BreadCrumb(course=course) }}
{{ CourseCardWide(course) }}
{{ CourseOutlineAndCreator(course) }}
{{ Mentors(course) }}
{{ CourseDescriptionAndOverview(course) }}
{{ widgets.Reviews(course=course, membership=membership) }}
</div>
</div>
{% endblock %}
<!-- Course Card -->
{% macro CourseCardWide(course) %}
<div class="common-card-style course-card-wide">
<div class="course-image-wide {% if not course.image %} default-image {% endif %}" {% if course.image
%}style="background-image: url({{ course.image }});" {% endif %}>
<div class="course-tags">
{% for tag in course.get_tags() %}
<div class="course-card-pills">{{ tag }}</div>
{% endfor %}
</div>
{% if not course.image %}
<div class="default-image-text">{{ course.title[0] }}</div>
{% endif %}
</div>
<div class="course-card-wide-content">
<div class="course-info">
<div class="course-card-wide-title">
{{ course.title }}
</div>
<div class="course-card-wide-intro">
{{ course.short_introduction }}
</div>
</div>
<div class="course-buttons">
{% if not course.disable_self_learning and not membership and not course.upcoming %}
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
Start Learning
<img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</div>
{% endif %}
{% if membership %}
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
membership.current_lesson
else '1.1' %}
<a class="button wide-button is-primary" id="continue-learning"
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">
Continue Learning <img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</a>
{% endif %}
{% if course.upcoming %}
<button class="button wide-button is-default" id="notify-me" data-course="{{course.name | urlencode}}" {% if
is_user_interested %} disabled="disabled" title="Your interest has already been noted." {% endif %}>
Notify me when available
</button>
{% endif %}
{% if course.video_link %}
<div class="button wide-button is-secondary video-preview">
Watch Video Preview
<img class="ml-2" src="/assets/school/images/play.png" />
</div>
{% endif %}
</div>
</div>
</div>
{% if course.video_link %}
<div class="modal fade preview-modal" id="video-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="course-home-headings modal-headings">{{ course.title }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<iframe class="video-iframe" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen src="{{ course.video_link }}"></iframe>
</div>
</div>
</div>
</div>
{% endif %}
{% endmacro%}
<!-- Course Outline and Creator -->
{% macro CourseOutlineAndCreator(course) %}
{% set certificate = course.is_certified() %}
<div class="course-outline-instructor-parent">
<div class="course-home-outline">
{{ widgets.CourseOutline(course=course, membership=membership) }}
</div>
<div class="course-creator-progress-parent">
<div class="course-creator-section">
<div class="course-home-headings">
Creator
</div>
{{ widgets.MemberCard(member=course.get_instructor(), show_course_count=True, dimension_class="member-card-large") }}
</div>
{% set progress = course.get_course_progress() %}
{% if progress %}
<div class="course-progress-section">
<div class="course-home-headings">
Your Progress
</div>
<div class="common-card-style progress-card">
<p class="small-title">
{% if progress != 100 %}
Great work so far!
{% else %}
Excellent work on completing this course 👏
{% endif %}
</p>
<p class="progress-text">
{% if progress != 100 %}
Challenge yourself to complete the lessons and grow professionally.
{% else %}
You have reached a new level in your journey to success!
{% endif %}
</p>
<div class="progress-percentage">
{{ frappe.utils.rounded(progress) }}%
</div>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{ progress }}%" aria-valuenow="{{ progress }}"
aria-valuemin="0" aria-valuemax="100"></div>
</div>
{% if certificate %}
<a class="muted-text dark-links mt-5 text-center" href="/courses/{{ course.name }}/{{ certificate }}">Get
Certificate</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endmacro %}
<!-- Mentors -->
{% macro Mentors(course) %}
{% if course.get_mentors() | length %}
<div class="course-home-mentors">
<div class="course-home-headings">
Mentors
</div>
<div class="mentors-section">
{% for mentor in course.get_mentors() %}
{{ widgets.MemberCard(member=mentor, show_course_count=False, dimension_class="") }}
{% endfor %}
</div>
<div class="view-all-mentors">
<span class="card-divider-dark flex-one"></span>
<span class="course-instructor"><span class="all-mentors-text">View all mentors</span> <img class="mentor-icon"
src="/assets/school/icons/down-arrow.svg" /></span>
<span class="card-divider-dark flex-one"></span>
</div>
</div>
{% endif %}
{% endmacro %}
<!-- Course Description and Overview -->
{% macro CourseDescriptionAndOverview(course) %}
<div class="course-outline-instructor-parent">
<div class="course-description-section">
<div class="course-home-headings">
Course Description
</div>
<div class="common-card-style description-card">
{{ frappe.utils.md_to_html(course.description) }}
</div>
</div>
{% set avg_rating = course.get_average_rating() %}
{% if course.get_students() | length or avg_rating %}
<div class="course-overview-section">
<div class="course-home-headings">
Overview
</div>
<div class="common-card-style overview-card small-title">
{% if course.get_students() | length %}
<div class="overtime-item">
<img class="icon-background mr-1" src="/assets/school/icons/user.svg" />
{{ course.get_students() | length }} Enrolled
</div>
{% endif %}
{% if avg_rating %}
<div class="overtime-item">
<img class="icon-background mr-1" src="/assets/school/icons/rating.svg" />
{{ avg_rating }} Rating
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -0,0 +1,222 @@
frappe.ready(() => {
if (frappe.session.user != "Guest") {
check_mentor_request();
}
hide_wrapped_mentor_cards();
$("#apply-now").click((e) => {
create_mentor_request(e);
});
$("#cancel-request").click((e) => {
cancel_mentor_request(e);
});
$(".join-batch").click((e) => {
join_course(e)
});
$(".view-all-mentors").click((e) => {
view_all_mentors(e);
});
$(".video-preview").click((e) => {
show_video_dialog(e);
});
$(".review-link").click((e) => {
show_review_dialog(e);
});
$(".icon-rating").click((e) => {
highlight_rating(e);
});
$("#submit-review").click((e) => {
submit_review(e);
})
$("#notify-me").click((e) => {
notify_user(e);
})
})
var check_mentor_request = () => {
frappe.call({
'method': 'school.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested',
'args': {
course: decodeURIComponent($("#course-title").attr("data-course")),
},
'callback': (data) => {
if (data.message > 0) {
$("#mentor-request").addClass("hide");
$("#already-applied").removeClass("hide")
}
}
})
}
var hide_wrapped_mentor_cards = () => {
var offset_top_prev;
$(".mentors-section .member-card").each(function () {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped').slideUp("fast");
}
if (!offset_top_prev) {
offset_top_prev = offset_top;
}
});
if ($(".wrapped").length < 1) {
$(".view-all-mentors").hide();
}
}
var create_mentor_request = (e) => {
e.preventDefault();
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
return;
}
frappe.call({
"method": "school.lms.doctype.lms_mentor_request.lms_mentor_request.create_request",
"args": {
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
},
"callback": (data) => {
if (data.message == "OK") {
$("#mentor-request").addClass("hide");
$("#already-applied").removeClass("hide")
}
}
})
}
var cancel_mentor_request = (e) => {
e.preventDefault()
frappe.call({
"method": "school.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request",
"args": {
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
},
"callback": (data) => {
if (data.message == "OK") {
$("#mentor-request").removeClass("hide");
$("#already-applied").addClass("hide")
}
}
})
}
var join_course = (e) => {
e.preventDefault();
var course = $(e.currentTarget).attr("data-course")
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
var batch = $(e.currentTarget).attr("data-batch");
batch = batch ? decodeURIComponent(batch) : "";
frappe.call({
"method": "school.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch ? batch : "",
"course": course
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint(__("You are now a student of this course."));
setTimeout(function () {
window.location.href = `/courses/${course}/learn/1.1`;
}, 2000);
}
}
})
}
var view_all_mentors = (e) => {
$(".wrapped").each((i, element) => {
$(element).slideToggle("slow");
})
var text_element = $(".view-all-mentors .course-instructor .all-mentors-text");
var text = text_element.text() == "View all mentors" ? "View less" : "View all mentors";
text_element.text(text);
if ($(".mentor-icon").css("transform") == "none") {
$(".mentor-icon").css("transform", "rotate(180deg)");
} else {
$(".mentor-icon").css("transform", "");
}
}
var show_video_dialog = (e) => {
e.preventDefault();
$("#video-modal").modal("show");
}
var show_review_dialog = (e) => {
e.preventDefault();
$("#review-modal").modal("show");
}
var highlight_rating = (e) => {
var rating = $(e.currentTarget).attr("data-rating");
$(".icon-rating").removeClass("star-click");
$(".icon-rating").each((i, elem) => {
if (i <= rating-1) {
$(elem).addClass("star-click");
}
})
}
var submit_review = (e) => {
e.preventDefault();
var rating = $(".rating-field").children(".star-click").length;
var review = $(".review-field").val();
if (!review || !rating) {
$(".error-field").text("Both Rating and Review are required.");
return;
}
frappe.call({
method: "school.lms.doctype.lms_course_review.lms_course_review.submit_review",
args: {
"rating": rating,
"review": review,
"course": decodeURIComponent($(e.currentTarget).attr("data-course"))
},
callback: (data) => {
if (data.message == "OK") {
$(".review-modal").modal("hide");
frappe.msgprint("Thanks for providing your feedback!");
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
})
}
var notify_user = (e) => {
e.preventDefault();
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
frappe.call({
method: "school.lms.doctype.lms_course_interest.lms_course_interest.capture_interest",
args: {
"course": course
},
callback: (data) => {
frappe.msgprint(__("Your interest has been noted. We'll notify you via email when this course becomes available."));
$("#notify-me").attr("disabled", true).attr("title", "Your interest has already been noted");
}
})
}

View File

@@ -0,0 +1,35 @@
import frappe
def get_context(context):
context.no_cache = 1
try:
course_name = frappe.form_dict["course"]
except KeyError:
frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect
course = frappe.get_doc("LMS Course", course_name)
if course is None:
frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect
context.course = course
membership = course.get_membership(frappe.session.user)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.membership = membership
if context.course.upcoming:
context.is_user_interested = get_user_interest(context.course.name)
context.metatags = {
"title": course.title,
"image": course.image,
"description": course.short_introduction,
"keywords": course.title
}
def get_user_interest(course):
return frappe.db.count("LMS Course Interest",
{
"course": course,
"user": frappe.session.user
})

View File

@@ -0,0 +1,37 @@
{% extends "templates/base.html" %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% block title %}{{ 'Courses' }}{% endblock %}
{% block head_include %}
<style>
</style>
{% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container">
{% if live_courses | length %}
<div class="courses-header">
{{ _('Live Courses') }}
</div>
<div class="cards-parent">
{% for course in live_courses %}
{{ widgets.CourseCard(course=course, read_only=False) }}
{% endfor %}
</div>
{% endif %}
{% if upcoming_courses | length %}
<div class="courses-header mt-12">
{{ _('Upcoming Courses') }}
</div>
<div class="cards-parent">
{% for course in upcoming_courses %}
{{ widgets.CourseCard(course=course, read_only=False) }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
import frappe
def get_context(context):
context.no_cache = 1
context.live_courses, context.upcoming_courses = get_courses()
context.metatags = {
"title": "All Courses",
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
"description": "This page lists all the courses published on our website",
"keywords": "All Courses, Courses, Learn"
}
def get_courses():
course_names = frappe.get_all("LMS Course",
filters={"is_published": True},
fields=["name", "upcoming"])
live_courses, upcoming_courses = [], []
for course in course_names:
if course.upcoming:
upcoming_courses.append(frappe.get_doc("LMS Course", course.name))
else:
live_courses.append(frappe.get_doc("LMS Course", course.name))
return live_courses, upcoming_courses

View File

View File

View File

@@ -0,0 +1,129 @@
{% extends "templates/base.html" %}
{% block title %}{{ hackathon }}{% endblock %}
{% from "www/hackathons/macros/hero.html" import hero %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% from "www/hackathons/macros/navbar.html" import navbar %}
{% from "www/hackathons/macros/user.html" import show_user %}
{% block head_include %}
<style>
div.card-hero-img {
height: 220px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: rgb(250, 251, 252);
}
.card-image-wrapper {
display: flex;
overflow: hidden;
height: 220px;
background-color: rgb(250, 251, 252);
justify-content: center;
}
.image-body {
align-self: center;
color: #d1d8dd;
font-size: 24px;
font-weight: 600;
line-height: 1;
padding: 20px;
}
section {
padding: 5rem 0 5rem 0;
}
</style>
{% endblock %}
{% macro card(project) %}
<div class="col-sm-4 mb-4 text-left">
<a href="/hackathons/{{ hackathon }}/{{ project.name }}" class="no-decoration no-underline">
<div class="card h-100">
<div class='card-body'>
<h5 class='card-title'>{{ project.name }}</h5>
<div class="text-muted">{{ project.project_short_intro }}</div>
</div>
</div>
</a>
</div>
{% endmacro %}
{% macro card_talk(talk) %}
<div class="col-sm-4 mb-4 text-left">
<a href="{{talk.video_link}}" class="no-decoration no-underline">
<div class="card h-100">
<div class='card-body'>
<h5 class='card-title'>{{ talk.topic }}</h5>
<div class="text-muted">{{ talk.speaker }}</div>
<div class="text-muted">{{ frappe.utils.format_datetime(talk.date_and_time, "medium") }}</div>
</div>
</div>
</a>
</div>
{% endmacro %}
{% macro card_update(update) %}
<div class="col-sm-4 mb-4 text-left">
<div class="card h-100">
<div class='card-body'>
<p>{{ frappe.utils.md_to_html(update.project_update) }}</p>
<div>
<a href="/hackathons/{{hackathon}}/{{update.project}}">{{ update.project}}</a>
by {{ show_user(update.owner) }}
<div class="text-muted">{{ frappe.utils.format_datetime(update.creation, "medium") }}</div>
</div>
</div>
</div>
</div>
{% endmacro %}
{% block content %}
<section class="section">
{{ hero(hackathon, {'name': 'Home', 'url': '/hackathons'}) }}
<div class='container'>
{{ navbar(hackathon) }}
<div class="tab-content">
<div class="tab-pane fade py-4 show active" role="tabpanel" id="home">
<div class="row mt-5">
{% for project in projects %}
{{ card(project) }}
{% endfor %}
{% if projects %}
{% for n in range( (3 - (projects|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
</div>
</div>
<div class="tab-pane fade py-4" role="tabpanel" id="talks">
<div class="row mt-5">
{% for talk in talks %}
{{ card_talk(talk) }}
{% endfor %}
{% if talks %}
{% for n in range( (3 - (talks|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
</div>
</div>
<div class="tab-pane fade py-4" role="tabpanel" id="updates">
<div class="row mt-5">
{% for update in updates %}
{{ card_update(update) }}
{% endfor %}
{% if updates %}
{% for n in range( (3 - (updates|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,25 @@
from __future__ import unicode_literals
import frappe
from frappe import _
def get_context(context):
context.no_cache = 1
try:
hackathon = frappe.form_dict['hackathon']
except KeyError:
frappe.local.flags.redirect_location = '/hackathons'
raise frappe.Redirect
context.projects = get_hackathon_projects(hackathon)
context.hackathon = hackathon
context.talks = get_hackathon_talks(hackathon)
context.updates = get_hackathon_updates(context.projects)
def get_hackathon_projects(hackathon):
return frappe.get_all("Community Project", filters={"hackathon":hackathon}, fields=["name", "project_short_intro"])
def get_hackathon_talks(hackathon):
return frappe.get_all("Community Talk", {"event": hackathon}, ["topic", "speaker", "date_and_time", "video_link"])
def get_hackathon_updates(projects):
project_list = [project.name for project in projects]
return frappe.get_all("Community Project Update", {"project": ["in", project_list]}, ["project", "`update` as project_update", "owner", "creation"])

View File

@@ -0,0 +1,63 @@
{% extends "templates/base.html" %}
{% block title %}{{ 'Hackathons' }}{% endblock %}
{% from "www/hackathons/macros/card.html" import hackathon_card %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% block head_include %}
<meta name="description" content="{{ 'Hackathon' }}" />
<meta name="keywords" content="An app that supports Communities." />
<style>
div.card-hero-img {
height: 220px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: rgb(250, 251, 252);
}
.card-image-wrapper {
display: flex;
overflow: hidden;
height: 220px;
background-color: rgb(250, 251, 252);
justify-content: center;
}
.image-body {
align-self: center;
color: #d1d8dd;
font-size: 24px;
font-weight: 600;
line-height: 1;
padding: 20px;
}
section {
padding: 5rem 0 5rem 0;
}
</style>
{% endblock %}
{% block content %}
<section class="top-section" style="padding: 6rem 0rem;">
<div class='container pb-5'>
<h1>{{ 'Hackathon' }}</h1>
<!-- <p class="mt-4">
{% if frappe.session.user == 'Guest' %}
<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
{% endif %}
</p> -->
</div>
<div class='container'>
<div class="row mt-5">
{% for hackathon in hackathons %}
{{ hackathon_card(hackathon) }}
{% endfor %}
{% if hackathons %}
{% for n in range( (3 - (hackathons|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,8 @@
import frappe
def get_context(context):
context.no_cache = 1
context.hackathons = get_hackathons()
def get_hackathons():
return frappe.get_all("Community Hackathon")

View File

View File

@@ -0,0 +1,18 @@
{% macro hackathon_card(hackathon) %}
<div class="col-sm-4 mb-4 text-left">
<a href="/hackathons/{{ hackathon.name }}" class="no-decoration no-underline">
<div class="card h-100" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);">
<div class='card-body'>
<h5 class='card-title'>{{ hackathon.name }}</h5>
</div>
</div>
</a>
</div>
{% endmacro %}
{% macro null_card() %}
<div class="col-sm-4 mb-4 text-left">
<div class="h-100 d-none d-sm-block" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);border-radius: 0.25rem;background-color: rgb(250, 251, 252);">
</div>
</div>
{% endmacro %}

View File

@@ -0,0 +1,15 @@
{% macro hero(title, back) %}
<div class='container pb-5'>
<div class="mb-3">
<a href="{{ back.url }}" class="text-muted">
{{_('Back to')}} {{ _(back.name) }}
</a>
</div>
<h1>{{ title }}</h1>
<!-- <p class="mt-4">
{% if frappe.session.user == 'Guest' %}
<a id="signup" class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
{% endif %}
</p> -->
</div>
{% endmacro %}

View File

@@ -0,0 +1,16 @@
{% macro navbar(hackathon) %}
<ul class="nav nav-tabs mt-4" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home"
aria-selected="true">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link" id="talks-tab" data-toggle="tab" href="#talks" role="tab" aria-controls="talks"
aria-selected="false">Talks</a>
</li>
<li class="nav-item">
<a class="nav-link" id="updates-tab" data-toggle="tab" href="#updates" role="tab" aria-controls="updates"
aria-selected="false">Updates</a>
</li>
</ul>
{% endmacro %}

View File

@@ -0,0 +1,3 @@
{% macro show_user(user) %}
{{ frappe.db.get_value("User", user, "full_name") }}
{% endmacro %}

View File

@@ -0,0 +1,174 @@
{% extends "templates/base.html" %}
{% block title %}{{ hackathon }}{% endblock %}
{% from "www/hackathons/macros/hero.html" import hero %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% from "www/hackathons/macros/user.html" import show_user %}
{% block head_include %}
<meta name="description" content="{{ 'Hackathon' }}" />
<meta name="keywords" content="An app that supports Communities" />
<style>
div.card-hero-img {
height: 220px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: rgb(250, 251, 252);
}
.card-image-wrapper {
display: flex;
overflow: hidden;
height: 220px;
background-color: rgb(250, 251, 252);
justify-content: center;
}
.image-body {
align-self: center;
color: #d1d8dd;
font-size: 24px;
font-weight: 600;
line-height: 1;
padding: 20px;
}
section {
padding: 5rem 0 5rem 0;
}
</style>
{% endblock %}
{% block content %}
<section class="section">
{{ hero(project, {'name': hackathon, 'url': '/hackathons/' + hackathon}) }}
<div class='container'>
{% if project %}
<h1 class="mb-2">{{project.project_name}}</h1>
{% if frappe.session.user != "Guest" %}
{% if is_owner %}
<p>
<div class="badge badge-info">Owner</div>
</p>
{% endif %}
{% if is_member %}
<p>
<div class="badge badge-info">Member</div>
</p>
{% endif %}
{% endif %}
<p>{{ project.project_short_intro[:220] }}</p>
{% if project.repository_link %}
<a href="{{ project.repository_link }}" class="btn btn-default btn-sm" target="_blank">Respository</a>
{% endif %}
{% if project.video_link %}
<a href="{{ project.video_link }}" class="btn btn-default btn-sm" target="_blank">Video ▶️</a>
{% endif %}
<button class="btn btn-default btn-sm btn-like" data-project={{project.name}}>👍</button>
<ul class="nav nav-tabs mt-4" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home"
aria-selected="true">Readme</a>
</li>
<li class="nav-item">
<a class="nav-link" id="members-tab" data-toggle="tab" href="#members" role="tab"
aria-controls="members" aria-selected="false">Members ({{ confirmed_members|len + 1}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="updates-tab" data-toggle="tab" href="#updates" role="tab"
aria-controls="updates" aria-selected="false">Updates ({{ (updates|len) + 1 }})</a>
</li>
</ul>
<div class="tab-content">
<!-- readme -->
<div class="tab-pane fade show active py-4 markdown-style" id="home" role="tabpanel"
aria-labelledby="home-tab">
{{ frappe.utils.md_to_html(project.project_description or "No README created yet") }}
</div>
<div class="tab-pane fade py-4" id="members" role="tabpanel" aria-labelledby="members-tab">
<!-- members -->
<div class="list-group">
<!-- owner -->
<div class="list-group-item">{{ show_user(project.owner) }}</div>
<!-- all members -->
{% for member in members %}
{% set is_user = member.owner == frappe.session.user %}
{% set is_pending = is_user and member.status=="Pending" %}
{% if member.status == "Accepted" %}
<div class="list-group-item">
{{ show_user(member.owner) }}
{% if is_user %}
<button data-request-id="{{ member.name }}"
class="btn btn-sm btn-default btn-leave ml-4">Leave</button>
{% endif %}
</div>
{% elif member.status == "Pending" and is_owner %}
<div class="list-group-item">Join request from: <b>{{ show_user(member.owner) }}</b>
<p class="alert alert-warning mt-2">{{ member.intro }}</p>
<div class="my-3">
<button data-request-id="{{ member.name }}"
class="btn btn-sm btn-secondary btn-accept">Accept</button>
<button data-request-id="{{ member.name }}"
class="btn btn-sm btn-default btn-reject">Reject</button>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% if frappe.session.user != 'Guest' %}
<!-- join / pending -->
{% if not (my_project or is_member or is_pending) and project.accepting_members %}
<a class="btn btn-sm btn-secondary mt-2"
href="/join-request?new=1&project={{ project.name }}&project_name={{ project.project_name }}">Join
{{ project.project_name }}</a>
{% elif is_pending %}
<p class="alert alert-warning mt-2">Your application is pending</p>
{% endif %}
{% endif %}
</div>
<!-- updates -->
<div class="tab-pane fade py-4" id="updates" role="tabpanel" aria-labelledby="updates-tab">
{% macro add_update(update, date) %}
<div class='list-group-item'>
{{ frappe.utils.md_to_html(update or '') }}
<div class="small text-muted text-right">{{ frappe.utils.format_datetime(date, "medium") }}</div>
</div>
{% endmacro %}
{% if frappe.session.user != 'Guest' and (is_owner or is_member) %}
<p>
<a href="/project-update?new=1&project={{ project.name }}&hackathon={{ hackathon }}" class="btn btn-secondary btn-sm">Add
Update</a>
</p>
{% endif %}
<div class='list-group'>
<!-- updates -->
{% for update in updates %}
{{ add_update(update.project_update, update.creation) }}
{%
<!-- creation -->
{{ add_update("Project created by " + frappe.db.get_value('User', project.owner, 'full_name'),
project.creation) }}
</div>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,48 @@
$('#req-evals').on('click', () => {
frappe.msgprint("The evaluations have been moved to <a href='https://t.me/fossunited'>Telegram</a>")
})
var set_likes = function (liked, likes) {
let $btn = $('.btn-like');
likes ? $btn.text(`${likes} 👍`): $btn.text(`👍`);
if (liked) {
$btn.addClass('btn-dark').removeClass('btn-default');
} else {
$btn.addClass('btn-default').removeClass('btn-dark');
}
};
// set initial likes
frappe.ready(() => {
frappe.call('school.www.hackathons.project.like', { project: get_url_arg().get("project"), initial: true }, (data) => {
set_likes(data.message.action == "Liked", data.message.likes)
})
})
var get_url_arg = () => {
return new URLSearchParams(window.location.search);
}
// like - unlike
$('.btn-like').on('click', (e) => {
frappe.call('school.www.hackathons.project.like', { project: get_url_arg().get("project") }, (data) => {
set_likes(data.message.action == "Liked", data.message.likes);
});
});
// accept / reject
$('.btn-accept').on('click', (e) => {
frappe.call('school.www.hackathons.project.join_request', { id: $(e.target).attr('data-request-id'), action: 'Accept' }, (data) => {
window.location.reload();
});
});
$('.btn-reject').on('click', (ev) => {
frappe.call('school.www.hackathons.project.join_request', { id: $(ev.target).attr('data-request-id'), action: 'Reject' }, (data) => {
window.location.reload();
});
});
$('.btn-leave').on('click', (ev) => {
frappe.call('school.www.hackathons.project.join_request', { id: $(ev.target).attr('data-request-id'), action: 'Reject' }, (data) => {
window.location.reload();
});
});

View File

@@ -0,0 +1,90 @@
from __future__ import unicode_literals
import frappe
from frappe import _
def get_context(context):
context.no_cache = 1
try:
project = frappe.form_dict['project']
hackathon = frappe.form_dict['hackathon']
except KeyError:
frappe.local.flags.redirect_location = '/hackathons'
raise frappe.Redirect
context.project = get_project(project)
context.hackathon = hackathon
context.members = get_members(project)
context.confirmed_members = get_comfirmed_members(project)
context.updates = get_updates(project)
if frappe.session.user != "Guest":
context.my_project = get_my_projects()
context.is_owner = context.project.owner == frappe.session.user
context.accepted_members = get_accepted_members(project)
context.is_member = check_is_member(project)
context.liked = get_liked_project(project)
def get_project(project_name):
try:
return frappe.get_doc('Community Project', project_name)
except frappe.DoesNotExistError:
frappe.throw(_("Project {0} does not exist.").format(project_name))
def get_members(project_name):
return frappe.get_all("Community Project Member", {"project": project_name, "status": ("!=", "Rejected") }, ['name', "owner", "status", 'intro'])
def get_comfirmed_members(project_name):
return frappe.get_all("Community Project Member", {"project": project_name, "status": ("=", "Accepted") }, ['name'])
def get_updates(project_name):
return frappe.get_all('Community Project Update', {"project": project_name}, ['owner', 'creation', '`update` as project_update'])
def get_accepted_members(project_name):
return frappe.get_all("Community Project Member", {"project": project_name, "status": "Accepted" })
def get_my_projects():
my_project = frappe.db.get_value('Community Project', {"owner": frappe.session.user})
if not my_project:
my_project = frappe.db.get_value('Community Project Member', {"owner": frappe.session.user, "status": 'Accepted'}, 'project')
return my_project
def check_is_member(project_name):
return frappe.get_all("Community Project Member", {"project": project_name, "status": "Accepted", "owner": frappe.session.user })
def get_liked_project(project_name):
return frappe.db.get_value("Community Project Like", {"owner": frappe.session.user, "project": project_name})
@frappe.whitelist()
def join_request(id, action):
if action == 'Accept':
project_member = frappe.get_doc('Community Project Member', id)
if len(frappe.db.get_all('Community Project Member',
dict(project = project_member.project, status = 'Accepted'))) > 2:
frappe.throw('A project cannot have more than 4 members')
frappe.db.set_value('Community Project Member', id, 'status', 'Accepted')
else:
frappe.db.set_value('Community Project Member', id, 'status', 'Rejected')
def has_already_liked(project):
likes = frappe.db.get_value('Community Project Like', {"owner": frappe.session.user, "project": project})
return likes
@frappe.whitelist()
def get_project_likes(project):
return len(frappe.get_all("Community Project Like", {"project": project}))
@frappe.whitelist()
def like(project, initial=False):
liked_project = has_already_liked(project)
action = "Liked" if (liked_project and initial) else "Unliked"
if not initial:
if liked_project:
action = "Unliked"
frappe.get_doc("Community Project Like", liked_project).delete()
else:
action = "Liked"
frappe.get_doc({"doctype": "Community Project Like","project": project}).save()
frappe.db.set_value("Community Project", project, "likes", get_project_likes(project))
return {
"action": action,
"likes": get_project_likes(project)
}

View File

View File

@@ -0,0 +1,21 @@
{% macro MentorsSection(mentors, is_mentor, course_name) %}
<h3>Mentors</h3>
{% for m in mentors %}
<div class="instructor">
<div class="instructor-title">{{m.full_name}}</div>
<div class="instructor-subtitle">Mentored {{m.get_batch_count()}} batches</div>
</div>
{% endfor %}
{% if not is_mentor %}
<div id="mentor-request" class="notice">
Interested to become a mentor?
<div><a id="apply-now" data-course="{{course_name | urlencode}}" href="">Apply Now!</a></div>
</div>
<div id="already-applied" class="notice hide">
You've applied to become a mentor for this course. Your request is currently under review.
If you are not any more interested to mentor this course, you can <a id="cancel-request" data-course="{{course_name | urlencode}}" href="">cancel your application</a>.
</div>
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,127 @@
{% macro LiveCodeEditorLarge(name, code) %}
<div class="livecode-editor livecode-editor-large" id="editor-{{name}}">
<div class="row">
<div class="col-lg-8 col-md-6">
<div class="controls">
<button class="run">Run</button>
</div>
</div>
</div>
<div class="code-editor">
<div class="row">
<div class="col-lg-8 col-md-6">
<div class="code-wrapper">
<textarea class="code">{{code}}</textarea>
</div>
</div>
<div class="col-lg-4 col-md-6 canvas-wrapper">
<canvas width="300" height="300"></canvas>
<pre class="output"></pre>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro LiveCodeEditor(name, code, reset_code, is_exercise=False, last_submitted=None) %}
<div class="livecode-editor livecode-editor-inline" id="editor-{{name}}">
<div class="row">
<div class="col-lg-8 col-md-6">
<div class="controls">
<button class="run">Run</button>
<button class="reset">Reset</button>
{% if is_exercise %}
<button class="submit pull-right btn-primary">Submit</button>
{% if last_submitted %}
<span class="pull-right" style="padding-right: 10px;"><span class="human-time" data-timestamp="{{last_submitted}}"></span></span>
{% endif %}
{% endif %}
</div>
<div style="display: none">
<pre class="reset-code">{{reset_code}}</pre>
</div>
</div>
</div>
<div class="code-editor">
<div class="row">
<div class="col-lg-8 col-md-6">
<div class="code-wrapper">
<textarea class="code">{{code}}</textarea>
</div>
</div>
<div class="col-lg-4 col-md-6 canvas-wrapper">
<canvas width="300" height="300"></canvas>
<pre class="output"></pre>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro LiveCodeEditorJS(name, code) %}
<script type="text/javascript" src="/assets/frappe/node_modules/moment/min/moment-with-locales.min.js"></script>
<script type="text/javascript" src="/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
<script type="text/javascript" src="/assets/frappe/js/frappe/utils/datetime.js"></script>
<script type="text/javascript">
// comment_when is failing because of this
if (!frappe.sys_defaults) {
frappe.sys_defaults = {}
}
</script>
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
<script type="text/javascript" src="/assets/school/js/livecode-canvas.js"></script>
<script type="text/javascript">
var livecodeEditors = [];
var livecodeEditorsMap = {};
$(function() {
$(".livecode-editor").each((i, e) => {
var name = e.id.replace("editor-", "");
var editor = new LiveCodeEditor(e, {
base_url: "{{ livecode_url }}",
...getLiveCodeOptions()
})
livecodeEditors.push(editor);
livecodeEditorsMap[e.id] = editor;
$(e).find(".reset").on('click', function() {
let code = $(e).find(".reset-code").html();
editor.codemirror.doc.setValue(code);
});
$(e).find(".submit").on('click', function() {
let code = editor.codemirror.doc.getValue();
console.log("submit", name, code);
frappe.call("school.lms.api.submit_solution", {
"exercise": name,
"code": code
}).then(r => {
if (r.message.name) {
frappe.msgprint("Submitted successfully!");
let d = r.message.creation;
$(e).find(".human-time").html(__("Submitted {0}", [comment_when(d)]));
}
});
});
});
});
function updateSubmitTimes() {
$(".human-time").each(function(i, e) {
var d = $(e).data().timestamp;
$(e).html(__("Submitted {0}", [comment_when(d)]));
});
}
updateSubmitTimes();
</script>
{% endmacro %}

View File

@@ -0,0 +1,70 @@
{% extends "templates/web.html" %}
{% block title %} {{_("New Sign Up")}} {% endblock %}
{% block page_content %}
<form id="new-sign-up">
<div class="form-group">
<label for="full_name">Full Name:</label>
<input id="full_name" type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="signup_email">Email:</label>
<input id="signup_email" type="email" class="form-control" required>
</div>
<div class="form-group">
<label for="username">Username:</label>
<input id="username" type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input id="password" type="password" class="form-control" required>
<span class="password-strength-indicator indicator"></span>
</div>
<p class='password-strength-message text-muted small hidden'></p>
<div class="form-group">
<label for="invite_code">Invite Code:</label>
<input id="invite_code" type="text" class="form-control" readonly required
value="{{ frappe.form_dict['invite_code'] }}">
</div>
<button type="submit" id="submit" class="btn btn-primary">{{_("Submit")}}</button>
</form>
<script>
frappe.ready(() => {
$("#submit").click(function () {
var data = {
full_name: $("#full_name").val(),
signup_email: $("#signup_email").val(),
username: $("#username").val(),
password: $("#password").val(),
invite_code: $("#invite_code").val(),
};
frappe.call({
type: "POST",
method: "school.lms.doctype.invite_request.invite_request.update_invite",
args: {
"data": data
},
callback: (data) => {
$("input").val("");
if (data.message == "OK") {
frappe.msgprint({
message: __("Your Account has been successfully created!"),
clear: true
});
setTimeout(function() {
window.location.href = "/login";
}, 2000);
}
}
});
return false;
});
})
</script>
{% endblock %}

View File

View File

@@ -0,0 +1,197 @@
{% extends "templates/base.html" %}
{% block head_include %}
<meta name="description" content="{{ member.full_name }}" />
<meta name="keywords" content="An app that supports Communities." />
{% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container profile-page">
{% set read_only = member.name != frappe.session.user %}
{{ ProfileBanner(member) }}
{{ AboutOverviewSection(member) }}
{{ CoursesEnrolled(member, read_only) }}
{{ CoursesCreated(member, read_only) }}
{{ CoursesMentored(member, read_only) }}
{{ ProfileTabs(profile_tabs) }}
</div>
</div>
{% endblock %}
{% macro ProfileBanner(member) %}
<div class="">
<div class="profile-banner" style="background-image: url(/assets/school/images/profile-banner.png)">
<div class="profile-avatar">
{{ widgets.Avatar(member=member, avatar_class="avatar-xl") }}
<div class="profile-name"> {{ member.full_name }} </div>
{% if member.get_authored_courses() | length %}
<div class="creator-badge"> Creator </div>
{% endif %}
</div>
</div>
<div class="profile-info">
<div class="profile-profession">
{% if member.profession %}
<span class=""> {{ member.profession }} </span>
{% endif %}
<span class="social-icons">
{% if member.linkedin %}
<a class="linkedin button-links" href="{{ member.linkedin }}">
<img src="/assets/school/images/linkedin.png">
</a>
{% endif %}
{% if member.medium %}
<a class="medium button-links" href="{{ member.medium}}">
<img src="/assets/school/icons/medium.svg">
</a>
{% endif %}
{% if member.github %}
<a class="github button-links" href="{{ member.github }}">
<img src="/assets/school/icons/github.svg">
</a>
{% endif %}
</span>
{% if frappe.session.user == member.email %}
<a class="dark-links pull-right" href="/edit-profile?name={{ member.email }}">Edit Profile</a>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
{% macro AboutOverviewSection(member) %}
{% set enrollment = member.get_course_membership("Student") | length %}
{% set mentorship = member.get_course_membership("Mentor") | length %}
{% set reviews = member.get_user_reviews() | length %}
<div class="profile-parent-section">
{% if member.bio %}
<div class="profile-about-section">
<div class="course-home-headings">
About
</div>
<div class="common-card-style description-card">
{{ member.bio }}
</div>
</div>
{% endif %}
{% if enrollment or reviews or mentorship %}
<div class="course-overview-section">
<div class="course-home-headings">
Overview
</div>
<div class="common-card-style overview-card small-title">
{% if enrollment %}
<div class="overtime-item">
<img class="icon-background mr-1" src="/assets/school/icons/user.svg" />
{{ enrollment }} Enrolled
</div>
{% endif %}
{% if reviews %}
<div class="overtime-item">
<img class="icon-background mr-1" src="/assets/school/icons/rating.svg" />
{{ reviews }} Created
</div>
{% endif %}
{% if mentorship %}
<div class="overtime-item">
<img class="icon-background mr-1" src="/assets/school/icons/calendar.svg" />
{{ mentorship }} Mentored
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endmacro %}
{% macro CoursesCreated(member, read_only) %}
{% if member.get_authored_courses() | length %}
<div class="profile-courses">
<div class="course-home-headings">
Courses Created
</div>
<div class="cards-parent">
{% for course in member.get_authored_courses() %}
{% set course_details = frappe.get_doc("LMS Course", course) %}
{{ widgets.CourseCard(course=course_details, read_only=read_only) }}
{% endfor %}
</div>
</div>
{% endif %}
{% endmacro %}
{% macro CoursesMentored(member, read_only) %}
{% if member.get_mentored_courses() | length %}
<div class="profile-courses">
<div class="course-home-headings">
Courses Mentored
</div>
<div class="cards-parent">
{% for mentorship in member.get_mentored_courses() %}
{% set course_details = frappe.get_doc("LMS Course", mentorship.course) %}
{{ widgets.CourseCard(course=course_details, read_only=read_only) }}
{% endfor %}
</div>
</div>
{% endif %}
{% endmacro %}
{% macro CoursesEnrolled(member, read_only) %}
{% set enrolled = member.get_enrolled_courses() %}
{% if enrolled.completed | length %}
<div class="profile-courses">
<div class="course-home-headings">
Courses Completed
</div>
<div class="cards-parent">
{% for course in enrolled.completed %}
{{ widgets.CourseCard(course=course, read_only=read_only) }}
{% endfor %}
</div>
</div>
{% endif %}
{% if enrolled.in_progress | length %}
<div class="profile-courses">
<div class="course-home-headings">
Courses In Progress
</div>
<div class="cards-parent">
{% for course in enrolled.in_progress %}
{{ widgets.CourseCard(course=course, read_only=read_only) }}
{% endfor %}
</div>
</div>
{% endif %}
{% endmacro %}
{% macro ProfileTabs(profile_tabs) %}
<div>
{% for tab in profile_tabs %}
{% set slug = title.lower().replace(" ", "-") %}
<div class="tab-content">
<div class="tab-pane fade py-4 show active" role="tabpanel" id="slug">
{{ tab.render() }}
</div>
</div>
{% endfor %}
</div>
{% endmacro %}
{% block script %}
<script>
frappe.ready(() => {
if ("{{ member.name }}" == frappe.session.user) {
setTimeout(() => {
var link_array = $('.nav-link').filter((i, elem) => $(elem).text().trim() === "My Profile");
link_array.length && $(link_array[0]).addClass("active");
}, 0)
}
})
</script>
{% endblock %}

View File

@@ -0,0 +1,29 @@
import frappe
from school.page_renderers import get_profile_url_prefix
from urllib.parse import urlencode
def get_context(context):
context.no_cache = 1
try:
username = frappe.form_dict["username"]
except KeyError:
username = frappe.db.get_value("User", frappe.session.user, ["username"])
if username:
frappe.local.flags.redirect_location = get_profile_url_prefix() + urlencode({"username": username})
raise frappe.Redirect
try:
context.member = frappe.get_doc("User", {"username": username})
except:
context.template = "www/404.html"
return
context.profile_tabs = get_profile_tabs(context.member)
def get_profile_tabs(user):
"""Returns the enabled ProfileTab objects.
Each ProfileTab is rendered as a tab on the profile page and the
they are specified as profile_tabs hook.
"""
tabs = frappe.get_hooks("profile_tabs") or []
return [frappe.get_attr(tab)(user) for tab in tabs]