Merge pull request #128 from fossunited/ui-fixes

fix: UI fixes
This commit is contained in:
Jannat Patel
2021-06-15 13:19:03 +05:30
committed by GitHub
50 changed files with 418 additions and 616 deletions

View File

@@ -5,11 +5,12 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
class CommunityProjectMember(Document):
def validate(self):
self.validate_if_already_member()
def validate_if_already_member(self):
if frappe.get_all("Community Project Member", {"owner": self.owner}):
frappe.throw(_("You have already applied for the membership of this project."))

View File

@@ -136,15 +136,15 @@ primary_rules = [
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/dashboard", "to_route": ""},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},
{"from_route": "/courses/<course>/<batch>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/<batch>/learn", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/schedule", "to_route": "batch/schedule"},
{"from_route": "/courses/<course>/<batch>/members", "to_route": "batch/members"},
{"from_route": "/courses/<course>/<batch>/discuss", "to_route": "batch/discuss"},
{"from_route": "/courses/<course>/<batch>/about", "to_route": "batch/about"},
{"from_route": "/courses/<course>/<batch>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/<batch>/join", "to_route": "batch/join"}
{"from_route": "/courses/<course>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/schedule", "to_route": "batch/schedule"},
{"from_route": "/courses/<course>/members", "to_route": "batch/members"},
{"from_route": "/courses/<course>/discuss", "to_route": "batch/discuss"},
{"from_route": "/courses/<course>/about", "to_route": "batch/about"},
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/join", "to_route": "batch/join"}
]
# Any frappe default URL is blocked by profile-rules, add it here to unblock it

View File

@@ -15,13 +15,6 @@ def autosave_section(section, code):
doc.insert()
return {"name": doc.name}
@frappe.whitelist()
def get_section(name):
"""Saves the code edited in one of the sections.
"""
doc = frappe.get_doc("LMS Section", name)
return doc and doc.as_dict()
@frappe.whitelist()
def submit_solution(exercise, code):
"""Submits a solution.

View File

@@ -1,8 +0,0 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Code Revision', {
// refresh: function(frm) {
// }
});

View File

@@ -1,58 +0,0 @@
{
"actions": [],
"creation": "2021-04-07 00:26:28.806520",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section",
"code",
"author"
],
"fields": [
{
"fieldname": "section",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Section",
"options": "LMS Section"
},
{
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "author",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Author",
"options": "User"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-04-14 11:26:19.628317",
"modified_by": "Administrator",
"module": "LMS",
"name": "Code Revision",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "section",
"track_changes": 1
}

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class CodeRevision(Document):
pass

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestCodeRevision(unittest.TestCase):
pass

View File

@@ -58,7 +58,8 @@ def create_invite_request(invite_email):
frappe.get_doc({
"doctype": "Invite Request",
"invite_email": invite_email
"invite_email": invite_email,
"status": "Approved"
}).save(ignore_permissions=True)
return "OK"

View File

@@ -18,7 +18,7 @@ class TestInviteRequest(unittest.TestCase):
filters={"invite_email": "test_invite@example.com"},
fieldname=["invite_email", "status", "signup_email"],
as_dict=True)
self.assertEqual(invite.status, "Pending")
self.assertEqual(invite.status, "Approved")
self.assertEqual(invite.signup_email, None)
def test_create_invite_request_update(self):

View File

@@ -8,11 +8,13 @@
"field_order": [
"chapter",
"lesson_type",
"include_in_preview",
"column_break_4",
"title",
"index_",
"index_label",
"body",
"sections"
"section_break_6",
"body"
],
"fields": [
{
@@ -47,23 +49,31 @@
"fieldtype": "Markdown Editor",
"label": "Body"
},
{
"fieldname": "sections",
"fieldtype": "Table",
"label": "Sections",
"options": "LMS Section"
},
{
"fieldname": "index_label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Index Label",
"read_only": 1
},
{
"default": "0",
"fieldname": "include_in_preview",
"fieldtype": "Check",
"label": "Include In Preview"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-06-01 05:30:48.127494",
"modified": "2021-06-11 19:03:23.138165",
"modified_by": "Administrator",
"module": "LMS",
"name": "Lesson",

View File

@@ -5,7 +5,6 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from ...section_parser import SectionParser
from ...md import markdown_to_html, find_macros
class Lesson(Document):
@@ -39,9 +38,6 @@ class Lesson(Document):
def render_html(self):
return markdown_to_html(self.body)
def get_sections(self):
return sorted(self.get('sections'), key=lambda s: s.index)
def get_exercises(self):
if not self.body:
return []
@@ -50,30 +46,6 @@ class Lesson(Document):
exercises = [value for name, value in macros if name == "Exercise"]
return [frappe.get_doc("Exercise", name) for name in exercises]
def make_lms_section(self, index, section):
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
s.type = section.type
s.id = section.id
s.label = section.label
s.contents = section.contents
s.index = index
return s
def get_next(self):
"""Returns the number for the next lesson.
The return value would be like 1.2, 2.1 etc.
It will be None if there is no next lesson.
"""
def get_prev(self):
"""Returns the number for the prev lesson.
The return value would be like 1.2, 2.1 etc.
It will be None if there is no next lesson.
"""
def get_progress(self):
return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status")
@@ -98,11 +70,7 @@ def save_progress(lesson, batch):
return
lesson_details = frappe.get_doc("Lesson", lesson)
dynamic_content = frappe.db.count("LMS Section",
filters={
"type": ["not in", ["example", "text"]],
"parent": lesson_details.name
})
dynamic_content = find_macros(lesson_details.body)
status = "Complete"
if dynamic_content:
@@ -121,12 +89,11 @@ def update_progress(lesson):
if frappe.db.exists("LMS Course Progress", {"lesson": lesson, "owner": user}):
course_progress = frappe.get_doc("LMS Course Progress", {"lesson": lesson, "owner": user})
course_progress.status = "Complete"
course_progress.save()
course_progress.save(ignore_permissions=True)
def all_dynamic_content_submitted(lesson, user):
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, ["name"], pluck="name")
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, pluck="name", ignore_permissions=True)
all_exercises_submitted = False
print(exercise_names)
query = {
"exercise": ["in", exercise_names],
"owner": user

View File

@@ -80,11 +80,6 @@ class LMSBatch(Document):
membership = self.get_membership(user)
return membership and membership.current_lesson
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.course}/{self.name}/learn/{lesson_number}"
@frappe.whitelist()
def save_message(message, batch):
doc = frappe.get_doc({

View File

@@ -13,7 +13,8 @@
"course",
"member_type",
"role",
"current_lesson"
"current_lesson",
"is_current"
],
"fields": [
{
@@ -80,11 +81,19 @@
"fieldtype": "Data",
"label": "Memeber Username",
"read_only": 1
},
{
"default": "0",
"fieldname": "is_current",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Currently Being Used",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-24 12:40:57.125694",
"modified": "2021-06-14 10:24:35.425498",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Batch Membership",

View File

@@ -53,3 +53,13 @@ def create_membership(batch, member=None, member_type="Student", role="Member"):
"member": member or frappe.session.user
}).save(ignore_permissions=True)
return "OK"
@frappe.whitelist()
def update_current_membership(batch, course, member=frappe.session.user):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course})
for membership in all_memberships:
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0)
current_membership = frappe.get_all("LMS Batch Membership", {"batch": batch, "member": member})
if len(current_membership):
frappe.db.set_value("LMS Batch Membership", current_membership[0].name, "is_current", 1)

View File

@@ -8,6 +8,7 @@ from frappe.model.document import Document
import json
from ...utils import slugify
from community.query import find, find_all
from frappe.utils import flt
class LMSCourse(Document):
@staticmethod
@@ -112,7 +113,7 @@ class LMSCourse(Document):
"""Returns all chapters of this course.
"""
# TODO: chapters should have a way to specify the order
return find_all("Chapter", course=self.name, order_by="creation")
return find_all("Chapter", course=self.name, order_by="index_")
def get_batch(self, batch_name):
return find("LMS Batch", name=batch_name, course=self.name)
@@ -186,6 +187,26 @@ class LMSCourse(Document):
exercise.save()
i += 1
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.name}/learn/{lesson_number}"
def get_current_batch(self, member=frappe.session.user):
current_membership = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name, "is_current": 1}, pluck="batch")
print(current_membership, member, self.name)
if len(current_membership):
return current_membership[0]
print(frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch"))
return frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch")
def get_all_memberships(self, member=frappe.session.user):
print(member)
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch", "is_current"])
print(all_memberships)
for membership in all_memberships:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return all_memberships
def get_outline(self):
return CourseOutline(self)
@@ -197,6 +218,7 @@ class CourseOutline:
self.lessons = self.get_lessons()
def get_next(self, current):
current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons)
try:
index = numbers.index(current)
@@ -205,6 +227,7 @@ class CourseOutline:
return None
def get_prev(self, current):
current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons)
try:
index = numbers.index(current)
@@ -228,7 +251,7 @@ class CourseOutline:
chapter_numbers = {c['name']: c['index_'] for c in self.chapters}
for lesson in lessons:
lesson['number'] = "{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_'])
lesson['number'] = flt("{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_']))
return lessons
@frappe.whitelist()

View File

@@ -1,66 +0,0 @@
{
"actions": [],
"creation": "2021-03-05 15:10:53.906006",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"label",
"type",
"contents",
"code",
"attrs",
"index",
"id"
],
"fields": [
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label"
},
{
"fieldname": "type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Type"
},
{
"fieldname": "contents",
"fieldtype": "Markdown Editor",
"label": "Contents"
},
{
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "attrs",
"fieldtype": "Long Text",
"label": "attrs"
},
{
"fieldname": "index",
"fieldtype": "Int",
"label": "Index"
},
{
"fieldname": "id",
"fieldtype": "Data",
"label": "id"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-19 18:55:26.019625",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Section",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class LMSSection(Document):
def __repr__(self):
return f"<LMSSection {self.label!r}>"
def get_exercise(self):
if self.type == "exercise":
return frappe.get_doc("Exercise", self.id)
def get_latest_code_for_user(self):
"""Returns the latest code for the logged in user.
"""
if not frappe.session.user or frappe.session.user == "Guest":
return self.contents
result = frappe.get_all('Code Revision',
fields=["code"],
filters={
"author": frappe.session.user,
"section": self.name
},
order_by="creation desc",
page_length=1)
if result:
return result[0]['code']
else:
return self.contents

View File

@@ -2,4 +2,4 @@
"""
from .doctype.lms_course.lms_course import LMSCourse as Course
from .doctype.lms_sketch.lms_sketch import LMSSketch as Sketch
from .doctype.lms_batch_membership.lms_batch_membership import LMSBatchMembership as Membership

View File

@@ -1,84 +0,0 @@
"""Utility to split the text in the topic into multiple sections.
{{ section(type="example", id="foo") }}
circle(100, 100, 50)
{{ end }}
"""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import List, Tuple, Dict, Iterator
RE_SECTION = re.compile(r"^\{\{\s(\w+)\s*(?:\((.*)\))?\s*\}\}\s*")
class SectionParser:
def parse(self, text: str) -> Iterator[Section]:
"""Parses given text into sections and return an iterator over sections.
"""
lines = text.splitlines()
marked_lines = self.parse_lines(lines)
return self.group_sections(marked_lines)
def parse_lines(self, lines: List[str]) -> List[Tuple[str, str, str]]:
for line in lines:
m = RE_SECTION.match(line)
if m:
yield m.group(1), self.parse_attrs(m.group(2)), None
else:
yield None, None, line
def parse_attrs(self, attrs_str: str) -> Dict[str, str]:
# XXX-Anand: Hack
code = "dict({})".format(attrs_str or "")
return eval(code)
def group_sections(self, marked_lines) -> Iterator[Section]:
index = 0
def make_section(type='text', id=None, label=None, **attrs):
nonlocal index
index += 1
id = id or f"section-{index}"
label = label or id
return Section(
type=type,
id=id,
label=label,
attrs=attrs)
section = make_section("text")
for mark, attrs, line in marked_lines:
if not mark:
section.append(line)
continue
yield section
if mark == 'end':
section = make_section(type='text')
else:
section = make_section(**attrs)
yield section
@dataclass
class Section:
"""One section of the Topic.
"""
type: str
id: str
label: str
contents: str = ""
attrs: dict = None
def append(self, line):
if not line.endswith("\n"):
line = line + "\n"
self.contents += line
def __repr__(self):
attrs = dict(type=self.type, id=self.id, label=self.label, **self.attrs)
attrs_str = ", ".join(f'{k}="{v}"' for k, v in attrs.items())
return f'<Section({attrs_str})>'

View File

@@ -11,7 +11,7 @@
"apply_document_permissions": 0,
"button_label": "Save",
"creation": "2021-04-20 11:37:49.135114",
"custom_css": ".datepicker.active {\n background-color: white;\n}",
"custom_css": ".datepicker.active {\n background-color: white;\n}\n\n[data-doctype=\"Web Form\"] {\n max-width: 720px;\n margin: 6rem auto;\n}",
"doc_type": "LMS Batch",
"docstatus": 0,
"doctype": "Web Form",
@@ -19,7 +19,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2021-06-02 15:52:06.383260",
"modified": "2021-06-14 15:28:08.206622",
"modified_by": "Administrator",
"module": "LMS",
"name": "add-a-new-batch",

View File

@@ -1,3 +0,0 @@
frappe.ready(function() {
// bind events here
})

View File

@@ -1,48 +0,0 @@
{
"accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 0,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 0,
"button_label": "Save",
"creation": "2021-04-15 13:32:14.171328",
"doc_type": "LMS Batch Membership",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2021-04-15 13:32:14.171328",
"modified_by": "Administrator",
"module": "LMS",
"name": "join-a-batch",
"owner": "Administrator",
"payment_button_label": "Buy Now",
"published": 1,
"route": "join-a-batch",
"route_to_success_link": 0,
"show_attachments": 0,
"show_in_grid": 0,
"show_sidebar": 0,
"sidebar_items": [],
"success_url": "/join-a-batch",
"title": "Join a Batch",
"web_form_fields": [
{
"allow_read_on_all_link_options": 0,
"fieldtype": "Attach",
"hidden": 0,
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
}
]
}

View File

@@ -1,7 +0,0 @@
from __future__ import unicode_literals
import frappe
def get_context(context):
# do your magic here
pass

View File

@@ -1,28 +1,49 @@
<div class="mt-5">
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted"> {{ course.title }}</span> {% endif %}
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a
class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted">
{{ course.title }}</span> {% endif %}
{% set all_memberships = course.get_all_memberships() %}
{% if all_memberships | length > 1 %}
<a class="nav-link pull-right" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
Switch Batch
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for membership in all_memberships %}
{% if not membership.is_current %}
<a class="dropdown-item switch-batch" href="#" data-batch="{{ membership.batch | urlencode }}" data-course="{{ course.name | urlencode }}">{{ membership.batch_title }}</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% if not batch %}
{% set display_class = "hide" %}
{% else %}
{% set display_class = "" %}
{% endif %}
<ul class="nav nav-tabs mt-4">
<li class="nav-item">
<a class="nav-link" id="home" href="/courses/{{course.name}}/{{batch.name}}/home">Home</a>
<li class="nav-item {{ display_class }}">
<a class="nav-link" id="home" href="/courses/{{course.name}}/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" id="learn" href="/courses/{{course.name}}/{{batch.name}}/learn">Learn</a>
<a class="nav-link" id="learn" href="/courses/{{course.name}}/learn">Lessons</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" id="schedule" href="/courses/{{course.name}}/{{batch.name}}/schedule">Schedule</a>
<a class="nav-link" id="schedule" href="/courses/{{course.name}}/schedule">Schedule</a>
</li> -->
<li class="nav-item">
<a class="nav-link" id="members" href="/courses/{{course.name}}/{{batch.name}}/members">Members</a>
<li class="nav-item {{ display_class }}">
<a class="nav-link" id="members" href="/courses/{{course.name}}/members">Members</a>
</li>
<li class="nav-item">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/{{batch.name}}/discuss">Discussion</a>
<li class="nav-item {{ display_class }}">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/discuss">Discussion</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" id="about" href="/courses/{{course.name}}/{{batch.name}}/about">About</a>
<a class="nav-link" id="about" href="/courses/{{course.name}}/about">About</a>
</li> -->
{% if batch.is_member(frappe.session.user, member_type="Mentor") %}
{% if batch and batch.is_member(frappe.session.user, member_type="Mentor") %}
<li class="nav-item">
<a class="nav-link" id="progress" href="/courses/{{course.name}}/{{batch.name}}/progress">Progress</a>
<a class="nav-link" id="progress" href="/courses/{{course.name}}/progress">Progress</a>
</li>
{% endif %}
</ul>
@@ -35,5 +56,21 @@
else {
$("#learn").addClass('active')
}
$(".switch-batch").click((e) => {
e.preventDefault();
var batch = decodeURIComponent($(e.currentTarget).attr("data-batch"));
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
batch: batch,
course: course
},
callback: (data) => {
window.location.reload();
}
})
})
})
</script>

View File

@@ -1,18 +1,54 @@
<div class="chapter-teaser">
<div class="teaser-body">
<h3 class="chapter-title"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</h3>
<div class="chapter-title mb-5 font-weight-bold"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</div>
<div class="chapter-description">
{{ chapter.description or "" }}
</div>
<div class="chapter-lessons">
{% for lesson in chapter.get_lessons() %}
<div class="lesson-teaser">
<a {% if show_link %} class="anchor_style" href="{{ batch.get_learn_url(course.get_lesson_index(lesson.name)) }}" {% endif %}>{{ lesson.title }}</a>
<a {% if show_link or lesson.include_in_preview %}
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}" {% else %} href="" class="no-preview"
{% endif %} data-course="{{ course.name }}">{{ lesson.title }}</a>
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
<a class="pull-right badge p-1 {{ lesson.get_slugified_class() }}"> <img class="progress-image" src="/assets/community/images/Vector.png"> {{ lesson.get_progress() }}</a>
<a class="ml-5 badge p-1 {{ lesson.get_slugified_class() }}"> <img class="progress-image"
src="/assets/community/images/Vector.png"> {{ lesson.get_progress() }}</a>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<script>
frappe.ready(() => {
var d;
$(".no-preview").click((e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
var message = __("Please enroll for this course to access the lesson.");
var label = __("Checkout Upcoming Batches");
var action = "checkout_upcoming_batches";
d = frappe.msgprint({
title: __("This lesson is not available for preview!"),
message: message,
primary_action: {
"label": label,
"client_action": action,
}
});
})
window.redirect_to_login = () => {
window.location.href = `/login?redirect-to=/courses/${$(".no-preview").attr("data-course")}`
}
window.checkout_upcoming_batches = () => {
if ($(".upcoming").length > 0) {
$('html,body').animate({ scrollTop: $(".upcoming").offset().top }, 300);
}
frappe.hide_msgprint();
}
})
</script>

View File

@@ -1,5 +1,7 @@
<h2>Course Outline</h2>
<div class="mt-5">
<h3> Course Outline </h3>
{% for chapter in course.get_chapters() %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}}
{% endfor %}
{% for chapter in course.get_chapters() %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}}
{% endfor %}
</div>

View File

@@ -1,5 +1,6 @@
<h3>Instructor</h3>
<div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div>
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div>
{{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }}
<a class="ml-1 instructor-title" href="/{{instructor.username}}">{{ instructor.full_name }}</a>
<div class="instructor-subtitle">Course Creator</div>
<!-- <div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div> -->
</div>

View File

@@ -1,6 +1,6 @@
<div class="batch">
<div class="batch-details">
<div>Session every {{batch.sessions_on}}</div>
<div class="">Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}}
</div>
@@ -18,7 +18,8 @@
<div class="cta">
<div class="">
{% if can_manage %}
<a href="/courses/{{course.name}}/{{batch.name}}/home" class="btn btn-primary">Manage</a>
<a href="" class="btn btn-primary manage-batch" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Manage</a>
{% elif can_join %}
<button class="join-batch btn btn-primary" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Join this Batch</button>

View File

@@ -82,6 +82,7 @@ body {
border-radius: 10px;
margin: 10px 0px;
background: white;
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);
border: 1px solid #ddc;
}
@@ -156,19 +157,6 @@ img.profile-photo {
line-height: 51px;
}
.anchor_style {
color: inherit;
}
a:hover {
text-decoration: none;
color: inherit;
}
.anchor_style:hover {
text-decoration: underline
}
section {
padding: 5rem 0 5rem 0;
}

View File

@@ -10,6 +10,7 @@ h2 {
.teaser-body {
padding: 20px;
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%)
}
.teaser-footer {
padding: 20px;
@@ -66,6 +67,20 @@ h2 {
}
}
.anchor_style {
color: inherit;
}
.anchor_style:hover {
text-decoration: none
}
.no-preview:hover {
cursor: pointer;
color: #2490ef;
}
section {
padding: 60px 0px;
}
@@ -162,7 +177,6 @@ section.lightgray {
// }
.instructor-title {
font-weight: bold;
color: black;
}
@@ -331,3 +345,9 @@ section.lightgray {
margin: 40px 0px 0px 20px;
}
}
.no-preview-message {
width: fit-content;
margin: 50px 0px 50px;
color: black;
}

View File

@@ -4,3 +4,5 @@ from . import utils
def get_context(context):
utils.get_common_context(context)
context.messages = context.batch.get_messages()
if not context.batch:
utils.redirect_to_lesson(context.course)

View File

@@ -8,31 +8,33 @@
{% endblock %}
{% block content %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/" + batch.name + "/join" %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/join?batch=" + batch.name %}
<div class="container mt-5">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div>
<!-- <div>
<h1 class="mt-5">{{ batch.title }}</h1>
</div>
<div class="course-details">
</div> -->
<div class="course-details mt-5">
{{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }}
</div>
<div class="w-25">
<h2>Batch Schedule</h2>
<h3>Batch Schedule</h3>
{{ widgets.RenderBatch(course=course, batch=batch) }}
</div>
{% if batch.description %}
<h2>Batch Details</h2>
{{ frappe.utils.md_to_html(batch.description) }}
<div class="mt-5">
<h3>Batch Details</h3>
{{ frappe.utils.md_to_html(batch.description) }}
</div>
{% endif %}
{% if course.is_mentor(frappe.session.user) %}
<div class="">
<h2> Invite Members </h2>
<a href="" class="anchor_style mr-5" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation
<h3> Invite Members </h3>
<a href="" class="" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation
Link</a>
<small id="copy-message" class="text-muted pull-right" style="display: none;">Copied to Clipboard.</small>
<small id="copy-message" class="text-muted" style="display: none;">Copied to Clipboard.</small>
</div>
{% endif %}
@@ -52,7 +54,7 @@
$("#copy-message").slideDown(function () {
setTimeout(function () {
$("#copy-message").slideUp();
}, 5000);
}, 2000);
});
})
})

View File

@@ -3,3 +3,5 @@ from . import utils
def get_context(context):
utils.get_common_context(context)
if not context.batch:
utils.redirect_to_lesson(context.course)

View File

@@ -13,9 +13,9 @@
<div class='page-card-head'>
<span class='indicator blue password-box'>Login Required</span>
</div>
<div class=''>Please log in to confirm to join the course {{ batch.course_title }}.</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 }}/{{ batch.name }}/join">{{_("Login")}}</a>
href="/login?redirect-to=/courses/{{ batch.course }}/join?batch={{ batch.name }}">{{_("Login")}}</a>
</div>
{% elif already_a_member %}
@@ -26,7 +26,7 @@
</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="/courses/{{batch.course}}/{{batch.name}}/home">{{_("Go to Batch Home")}}</a>
<a type="submit" id="batch-home" class="btn btn-primary w-100" href="">{{_("Go to Batch Home")}}</a>
</div>
{% else %}
@@ -38,23 +38,20 @@
<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" data-batch="{{ batch.name | urlencode }}"
data-course="{{ batch.course | urlencode }}">{{_("Confirm")}}</a>
<a type="submit" id="confirm" class="btn btn-primary w-100">{{_("Confirm")}}</a>
</div>
{% endif %}
{% endblock %}
{% block script %}
<script>
frappe.ready(() => {
var confirm_element = $("#confirm");
var batch = decodeURIComponent(confirm_element.attr("data-batch"));
var course = decodeURIComponent(confirm_element.attr("data-course"));
confirm_element.click((e) => {
frappe.ready(() => {
$("#confirm").click((e) => {
frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch
"batch": "{{ batch.name }}"
},
"callback": (data) => {
if (data.message == "OK") {
@@ -63,12 +60,25 @@
clear: true
});
setTimeout(function () {
window.location.href = "/courses/" + course + "/" + batch + "/home";
window.location.href = "/courses/{{ batch.course }}/home";
}, 2000);
}
}
})
})
$("#batch-home").click((e) => {
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
"batch": "{{ batch.name }}",
"course": "{{ batch.course}}"
},
callback: (data) => {
window.location.href = "/courses/{{ batch.course }}/home"
}
})
})
})
</script>
{% endblock %}

View File

@@ -25,9 +25,16 @@
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="lesson-page">
<h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-name="{{ lesson.name }}" data-batch="{{ batch.name }}">{{ lesson.title }}</h2>
<h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-name="{{ lesson.name }}" {% if batch %} data-batch="{{ batch.name }}" {% endif %}>{{ lesson.title }}</h2>
{% if batch or lesson.include_in_preview %}
{{ lesson.render_html() }}
{% else %}
<div class="no-preview-message">
<span>This lesson is not available for Preview. Please join a batch to access the complete course.</span>
<a href="/courses/{{ course.name }}">Checkout Upcoming Batches</a>
</div>
{% endif %}
{{ pagination(prev_chap, prev_url, next_chap, next_url) }}
</div>
@@ -53,18 +60,6 @@
{%- block script %}
{{ super() }}
<script type="text/javascript">
$(function() {
var batch_name = "{{ batch.name }}";
var lesson_name = "{{ lesson.name }}";
frappe.call("community.lms.api.save_current_lesson", {
"batch_name": batch_name,
"lesson_name": lesson_name
})
})
</script>
{% for ext in page_extensions %}
{{ ext.render_footer() }}
{% endfor %}

View File

@@ -1,5 +1,5 @@
frappe.ready(() => {
if (!$(".title").hasClass("is_mentor")) {
if ($(".title").attr("data-batch") && !$(".title").hasClass("is_mentor")) {
frappe.call({
method: "community.lms.doctype.lesson.lesson.save_progress",
args: {
@@ -8,4 +8,10 @@ frappe.ready(() => {
}
})
}
if ($(".title").attr("data-batch")) {
frappe.call("community.lms.api.save_current_lesson", {
"batch_name": $(".title").attr("data-batch"),
"lesson_name": $(".title").attr("data-name")
})
}
})

View File

@@ -1,6 +1,9 @@
from re import I
import frappe
from . import utils
from frappe.utils import cstr
from community.www import batch
def get_context(context):
utils.get_common_context(context)
@@ -10,10 +13,12 @@ def get_context(context):
lesson_number = f"{chapter_index}.{lesson_index}"
course_name = context.course.name
if not chapter_index or not lesson_index:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
frappe.local.flags.redirect_location = context.batch.get_learn_url(index_)
if context.batch:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
else:
index_ = "1.1"
frappe.local.flags.redirect_location = context.course.get_learn_url(index_)
raise frappe.Redirect
context.lesson = context.course.get_lesson(chapter_index, lesson_index)
@@ -25,16 +30,17 @@ def get_context(context):
next_ = outline.get_next(lesson_number)
context.prev_chap = get_chapter_title(course_name, prev_)
context.next_chap = get_chapter_title(course_name, next_)
context.next_url = context.batch.get_learn_url(next_)
context.prev_url = context.batch.get_learn_url(prev_)
context.next_url = context.course.get_learn_url(next_)
context.prev_url = context.course.get_learn_url(prev_)
context.page_extensions = get_page_extensions()
def get_chapter_title(course_name, lesson_number):
if not lesson_number:
return
chapter_index = lesson_number.split(".")[0]
lesson_index = lesson_number.split(".")[1]
lesson_split = cstr(lesson_number).split(".")
chapter_index = lesson_split[0]
lesson_index = lesson_split[1]
chapter_name = frappe.db.get_value("Chapter", {"course": course_name, "index_": chapter_index}, "name")
return frappe.db.get_value("Lesson", {"chapter": chapter_name, "index_": lesson_index}, "title")

View File

@@ -19,23 +19,19 @@
{% macro MembersList(members) %}
<div class="mt-5">
{% for member in members %}
<div class="row mb-5">
<div>
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
</div>
<div class="col">
<div class="row ml-1">
<a class="anchor_style" href="/{{member.username}}">
<div class="d-flex align-items-center">
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
<div class="d-flex flex-column ml-2">
<div class="d-flex">
<a class="anchor_style ml-2" href="/{{member.username}}">
<h3>{{ member.full_name }}</h3>
</a>
{% if course.is_mentor(member.name) %}
<div class="ml-2">
<div class="badge badge-success">Mentor</div>
</div>
<div class="badge badge-success ml-2 align-self-start">Mentor</div>
{% endif %}
</div>
{% if member.bio %}
<i>{{member.bio}}</i>
<i class="ml-2">{{member.bio}}</i>
{% endif %}
</div>
</div>

View File

@@ -3,4 +3,5 @@ from . import utils
def get_context(context):
utils.get_common_context(context)
print(context.members[0].bio)
if not context.batch:
utils.redirect_to_lesson(context.course)

View File

@@ -26,7 +26,7 @@
<div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="mentor-dashboard">
<h1>Batch Progress</h1>
<h3>Batch Progress</h3>
{% for exercise in report.exercises %}
<div class="exercise-submissions">
<h2>Exercise {{exercise.index_label}}: {{exercise.title}}</h2>

View File

@@ -19,7 +19,6 @@ class BatchReport:
def __init__(self, course, batch):
self.submissions = get_submissions(batch)
self.exercises = self.get_exercises(course.name)
self.submissions_by_exercise = defaultdict(list)
for s in self.submissions:
self.submissions_by_exercise[s.exercise].append(s)
@@ -34,7 +33,6 @@ def get_submissions(batch):
students = batch.get_students()
students_map = {s.email: s for s in students}
names, values = nparams("s", students_map.keys())
sql = """
select owner, exercise, name, solution, creation, image
from (
@@ -45,7 +43,6 @@ def get_submissions(batch):
""".format(names)
data = frappe.db.sql(sql, values=values, as_dict=True)
for row in data:
row['owner'] = students_map[row['owner']]
return data

View File

@@ -5,24 +5,26 @@ def get_common_context(context):
context.no_cache = 1
course_name = frappe.form_dict["course"]
batch_name = frappe.form_dict["batch"]
course = Course.find(course_name)
if not course:
context.template = "www/404.html"
return
batch_name = course.get_current_batch()
batch = course.get_batch(batch_name)
if not batch or not batch.is_member(frappe.session.user):
frappe.local.flags.redirect_location = "/courses/" + course_name
raise frappe.Redirect
context.batch = batch
if batch_name:
context.members = batch.get_mentors() + batch.get_students()
context.member_count = len(context.members)
context.course = course
context.batch = batch
context.members = batch.get_mentors() + batch.get_students()
context.member_count = len(context.members)
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_)
raise frappe.Redirect

View File

@@ -12,28 +12,20 @@
<div class="mb-5">
<a class="anchor_style" href="/courses">Courses</a> / <span class="text-muted">{{ course.title }}</span>
</div>
<h1 id="course-title" data-course="{{course.name}}">{{course.title}}</h1>
<h2 id="course-title" data-course="{{course.name}}">{{course.title}}</h2>
<div class="course-short-intro">{{ course.short_introduction }}</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-12">
<div class="">
<div class="">
<div class="course-details">
{{ CourseVideo(course) }}
{{ CourseDescription(course) }}
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
{{ BatchSection(course) }}
{{ widgets.CourseOutline(course=course, show_link=False) }}
</div>
</div>
<div class="col-lg-4 col-md-12">
<div class="sidebar">
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
</div>
<div class="sidebar">
{{ MentorsSection(course.get_mentors(), course.is_mentor(frappe.session.user), course.name) }}
</div>
</div>
</div>
</div>
{% endblock %}
@@ -49,28 +41,31 @@
{% endmacro %}
{% macro CourseDescription(course) %}
<h2>Course Description</h2>
<div class="mt-5">
<h3>Course Description</h3>
<div class="course-description">
{{ frappe.utils.md_to_html(course.description) }}
<div class="course-description text-justify">
{{ frappe.utils.md_to_html(course.description) }}
</div>
</div>
{% endmacro %}
{% macro BatchSection(course) %}
{% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %}
<div class="row">
<div class="col-lg-8 col-md-12">
{% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %}
</div>
</div>
{% endmacro %}
{% macro BatchSectionForMentors(course, mentor_batches) %}
<h2>Your Batches</h2>
{% if mentor_batches %}
<!-- <div class="alert alert-secondary">
You are a mentor for this course. Manage your batches or create a new batch from here.
</div> -->
<div class="row">
{% for batch in mentor_batches %}
@@ -92,14 +87,17 @@
{% macro BatchSectionForStudents(course, upcoming_batches) %}
{% if upcoming_batches %}
<h2>Upcoming Batches</h2>
<div class="row">
{% for batch in upcoming_batches %}
<div class="col-lg-4 col-md-6">
{{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }}
<div class="mt-5">
<h3 class="upcoming">Upcoming Batches</h3>
<div class="row">
{% for batch in upcoming_batches %}
<div class="col-lg-4 col-md-6">
{{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% else %}
<div class="mt-5 upcoming">There are no Upcoming Batches for this course currently.</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -1,72 +1,92 @@
frappe.ready(() => {
if (frappe.session.user != "Guest") {
frappe.call({
'method': 'community.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")
}
}
})
}
if (frappe.session.user != "Guest") {
frappe.call({
'method': 'community.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")
}
}
})
}
$("#apply-now").click((e) => {
$("#apply-now").click((e) => {
e.preventDefault();
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
return;
}
frappe.call({
"method": "community.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")
}
}
})
})
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
return;
}
frappe.call({
"method": "community.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")
}
}
})
})
$("#cancel-request").click((e) => {
$("#cancel-request").click((e) => {
e.preventDefault()
frappe.call({
"method": "community.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")
}
}
})
})
frappe.call({
"method": "community.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")
}
}
})
})
$(".join-batch").click((e) => {
e.preventDefault()
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`;
return;
}
batch = decodeURIComponent($(e.currentTarget).attr("data-batch"))
frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint(__("You are now a student of this course."))
}
}
})
})
$(".join-batch").click((e) => {
e.preventDefault();
var course = $(e.currentTarget).attr("data-course")
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
batch = decodeURIComponent($(e.currentTarget).attr("data-batch"))
frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint(__("You are now a student of this course."));
setTimeout(function () {
window.location.href = `/courses/${course}/home`;
}, 2000);
}
}
})
})
$(".manage-batch").click((e) => {
e.preventDefault();
var batch = decodeURIComponent($(e.currentTarget).attr("data-batch"));
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
batch: batch,
course: course
},
callback: (data) => {
window.location.href = `/courses/${course}/home`;
}
})
})
})

View File

@@ -19,6 +19,6 @@ def get_context(context):
batch = course.get_student_batch(frappe.session.user)
if batch:
frappe.local.flags.redirect_location = f"/courses/{course.name}/{batch.name}/learn"
frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
raise frappe.Redirect

View File

@@ -10,19 +10,17 @@
{% block content %}
<section class="top-section" style="padding: 1rem 0rem;">
<div class='container pb-5'>
<h1>{{ 'Courses' }}</h1>
</div>
<div class='container'>
<h4 class="mt-5">{{ 'All Courses' }}</h4>
<div class="row mt-5">
{% for course in courses %}
{{ course_card(course) }}
{% endfor %}
{% if courses %}
<!-- {% if courses %}
{% for n in range( (3 - (courses|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% endif %}
{% endif %} -->
</div>
</div>
</section>
@@ -31,8 +29,8 @@
{% macro course_card(course) %}
<div class="col-sm-4 mb-4 text-left">
<a class="card-links" style="color: inherit;" href="/courses/{{course.name}}">
<div class="card h-100">
<a class="anchor_style" style="color: inherit;" href="/courses/{{course.name}}">
<div class="card h-100" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);">
<div class='card-body'>
<h5 class='card-title'>{{ course.title }}</h5>
{% if course.description %}

View File

@@ -1,7 +1,7 @@
{% 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">
<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>
@@ -12,7 +12,7 @@
{% macro null_card() %}
<div class="col-sm-4 mb-4 text-left">
<div class="h-100 d-none d-sm-block" style="border: 1px solid rgba(209,216,221,0.5);border-radius: 0.25rem;background-color: rgb(250, 251, 252);">
<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 %}
{% endmacro %}