Compare commits

..

18 Commits

Author SHA1 Message Date
pateljannat
ff1363b437 fix: certificate, profile, quiz, and video markdown 2021-08-25 21:01:13 +05:30
Jannat Patel
952e3a9906 Merge pull request #190 from fossunited/web-form-changes
fix: web form issues
2021-08-24 21:23:23 +05:30
pateljannat
9d530e35fb fix: web form issues 2021-08-24 20:58:12 +05:30
Jannat Patel
2c2ad78eb7 Merge pull request #189 from sumaiya2908/event-registration
fix: attendee-route
2021-08-24 18:09:27 +05:30
Summayya
f61c5a2fa1 fix: implicit user in speaker and exhibitor 2021-08-24 17:47:58 +05:30
Summayya
3e24ff9678 fix: implicit user 2021-08-24 17:27:16 +05:30
Summayya
b0280c3be4 fix: attendee-route 2021-08-24 16:40:46 +05:30
Jannat Patel
b10eb5c979 Merge pull request #188 from fossunited/talks-thumbnail
fix: talk card schedule
2021-08-24 12:27:13 +05:30
pateljannat
e2072c72da fix: talk card schedule 2021-08-24 12:12:55 +05:30
Jannat Patel
84a43912db Merge pull request #186 from fossunited/fixes
fix: minor issues
2021-08-23 18:52:14 +05:30
pateljannat
841819436a fix: minor issues 2021-08-23 18:22:36 +05:30
pateljannat
14a984c75f Merge branch 'main' of https://github.com/frappe/community into fixes 2021-08-23 11:14:35 +05:30
Jannat Patel
445de61ce4 Merge pull request #185 from fossunited/web-form-url-fixes
fix: web form redirects
2021-08-23 10:32:40 +05:30
Jannat Patel
aefee791ca Merge pull request #184 from sumaiya2908/event-management
fix: redirections
2021-08-23 10:21:24 +05:30
Summayya
00154d80df fix: redirections 2021-08-23 10:08:43 +05:30
Summayya
a1e12d29ac fix: redirections 2021-08-23 10:02:06 +05:30
pateljannat
c6d3994383 fix: course page 2021-08-20 14:23:18 +05:30
pateljannat
eaec991f47 fix: chapter teaser drawer 2021-08-19 10:06:39 +05:30
41 changed files with 360 additions and 286 deletions

View File

@@ -28,9 +28,9 @@ order_by="creation") %}
<div class="d-flex flex-column align-items-center font-weight-bold"> <div class="d-flex flex-column align-items-center font-weight-bold">
Want to join the discussion? Want to join the discussion?
{% if frappe.session.user == "Guest" %} {% if frappe.session.user == "Guest" %}
<div class="button is-primary" id="login-from-discussion">Log In</div> <div class="button is-primary mt-5" id="login-from-discussion">Log In</div>
{% elif not condition %} {% elif not condition %}
<div class="button is-primary" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}</div> <div class="button is-primary mt-5" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}</div>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}

View File

@@ -2,7 +2,13 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Speaker', { frappe.ui.form.on('Speaker', {
// refresh: function(frm) { onload: function (frm) {
frm.set_query('user', function (doc) {
// } return {
filters: {
"ignore_user_type": 1,
}
};
});
}
}); });

View File

@@ -41,8 +41,8 @@
}, },
{ {
"fieldname": "thumbnail", "fieldname": "thumbnail",
"fieldtype": "Data", "fieldtype": "Attach",
"label": "Preview Image (Link)" "label": "Preview Image"
}, },
{ {
"fieldname": "event", "fieldname": "event",
@@ -113,7 +113,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-08-20 17:02:51.609288", "modified": "2021-08-24 16:47:34.557010",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Event Management", "module": "Event Management",
"name": "Talk", "name": "Talk",

View File

@@ -4,7 +4,6 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class Talk(Document): class Talk(Document):
def before_save(self): def before_save(self):
if not self.speaker: if not self.speaker:
@@ -25,5 +24,6 @@ class Talk(Document):
"doctype": "Speaker", "doctype": "Speaker",
"event": self.event, "event": self.event,
"user": frappe.session.user "user": frappe.session.user
}).save(ignore_permissions=True) })
self.speaker = speaker speaker.save(ignore_permissions=True)
self.speaker = speaker.name

View File

@@ -1,7 +1,11 @@
frappe.ready(function () { frappe.ready(function () {
frappe.web_form.after_load = () => {
frappe.web_form.set_value("user", frappe.session.user);
}
frappe.web_form.after_save = () => { frappe.web_form.after_save = () => {
setTimeout(function () { setTimeout(function () {
window.location.href = '/event/conference2021/about'; window.location.href = '/about';
}, 2000); }, 2000);
} }
}) })

View File

@@ -19,24 +19,25 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2021-08-23 10:13:19.224367", "modified": "2021-08-24 19:57:25.516319",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Event Management", "module": "Event Management",
"name": "attendee-registration", "name": "attendee-registration",
"owner": "Administrator", "owner": "Administrator",
"payment_button_label": "Buy Now", "payment_button_label": "Buy Now",
"published": 1, "published": 1,
"route": "event/conference2021/attendee-registration", "route": "attendee-registration",
"route_to_success_link": 1, "route_to_success_link": 1,
"show_attachments": 0, "show_attachments": 0,
"show_in_grid": 0, "show_in_grid": 0,
"show_sidebar": 0, "show_sidebar": 0,
"sidebar_items": [], "sidebar_items": [],
"success_url": "/event/conference2021/about", "success_url": "/about",
"title": "Attendee Registration", "title": "Attendee Registration",
"web_form_fields": [ "web_form_fields": [
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,
"default": "",
"fieldname": "user", "fieldname": "user",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,

View File

@@ -1,7 +1,12 @@
frappe.ready(function () { frappe.ready(function () {
frappe.web_form.after_load = () => {
frappe.web_form.set_value("user", frappe.session.user);
}
frappe.web_form.after_save = () => { frappe.web_form.after_save = () => {
setTimeout(function () { setTimeout(function () {
window.location.href = '/event/conference2021/about'; window.location.href = '/about';
}, 2000); }, 2000);
} }
}) })

View File

@@ -1,7 +1,11 @@
frappe.ready(function () { frappe.ready(function () {
frappe.web_form.after_load = () => {
frappe.web_form.set_value("user", frappe.session.user);
}
frappe.web_form.after_save = () => { frappe.web_form.after_save = () => {
setTimeout(function () { setTimeout(function () {
window.location.href = '/event/conference2021/about'; window.location.href = '/about';
}, 2000); }, 2000);
} }
}) })

View File

@@ -20,7 +20,7 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2021-08-23 10:18:17.486228", "modified": "2021-08-24 19:57:06.806994",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Event Management", "module": "Event Management",
"name": "purpose-a-talk", "name": "purpose-a-talk",
@@ -74,6 +74,18 @@
"reqd": 0, "reqd": 0,
"show_in_filter": 0 "show_in_filter": 0
}, },
{
"allow_read_on_all_link_options": 0,
"fieldname": "thumbnail",
"fieldtype": "Attach",
"hidden": 0,
"label": "Preview Image",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,
"fieldname": "about", "fieldname": "about",
@@ -85,18 +97,6 @@
"read_only": 0, "read_only": 0,
"reqd": 1, "reqd": 1,
"show_in_filter": 0 "show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "attachment",
"fieldtype": "Attach",
"hidden": 0,
"label": "Attachment",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
} }
] ]
} }

View File

@@ -1,4 +1,7 @@
frappe.ready(function () { frappe.ready(function () {
frappe.web_form.after_load = () => {
frappe.web_form.set_value("user", frappe.session.user);
}
frappe.web_form.after_save = () => { frappe.web_form.after_save = () => {
setTimeout(function () { setTimeout(function () {
window.location.href = '/event/conference2021/propose-talk'; window.location.href = '/event/conference2021/propose-talk';

View File

@@ -11,22 +11,7 @@
{% set member = frappe.get_doc("User", exhibitor_doc.user) %} {% set member = frappe.get_doc("User", exhibitor_doc.user) %}
<div class="common-card-style member-card"> <div class="common-card-style member-card">
{% set color = member.get_palette() %} {{ widgets.Avatar(member=member, avatar_class="avatar-large")}}
<a class="button-links" href="/user/{{member.username}}">
<span class="avatar avatar-large" title="{{ member.full_name }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
title="{{ member.full_name }}">
</img>
{% else %}
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
{{ frappe.utils.get_abbr(member.full_name) }}
</span>
{% endif %}
</span>
</a>
<div class="small-title member-card-title"> <div class="small-title member-card-title">
{{ member.full_name }} {{ member.full_name }}
</div> </div>

View File

@@ -10,21 +10,7 @@
{% set member = frappe.get_doc("User", speaker_doc.user) %} {% set member = frappe.get_doc("User", speaker_doc.user) %}
<div class="common-card-style member-card"> <div class="common-card-style member-card">
{% set color = member.get_palette() %} {{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
<a class="button-links" href="/user/{{member.username}}">
<span class="avatar avatar-large" title="{{ member.full_name }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
title="{{ member.full_name }}">
</img>
{% else %}
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
{{ frappe.utils.get_abbr(member.full_name) }}
</span>
{% endif %}
</span>
</a>
<div class="small-title member-card-title"> <div class="small-title member-card-title">
{{ member.full_name }} {{ member.full_name }}

View File

@@ -4,40 +4,61 @@
<p class="section-description">{{ subtitle }}</p> <p class="section-description">{{ subtitle }}</p>
{%- endif -%} {%- endif -%}
<div class="cards-parent"> <div class="cards-parent">
{% for talk in talk_details %}
{% set talk_doc = frappe.db.get_value('Talk', talk.talk, ["title", "category", "speaker", "url", "thumbnail"], as_dict=True) %}
{% set speaker_info = frappe.db.get_value("Speaker", talk_doc.speaker, ["user"], as_dict=True) %}
{% for talk in talk_details %}
{% set talk_doc = frappe.db.get_value('Talk', talk.talk,
["title", "category", "speaker", "url", "thumbnail", "date", "start_time", "end_time"], as_dict=True) %}
{% set speaker_info = frappe.db.get_value("Speaker", talk_doc.speaker, ["user"], as_dict=True) %}
{% set member = frappe.get_doc("User", speaker_info.user) %} {% set member = frappe.get_doc("User", speaker_info.user) %}
{% if talk_doc.thumbnail %}
{% set thumbnail = talk_doc.thumbnail %}
{% else %}
{% set video_id = talk_doc.url and talk_doc.url.split("/")[-1] %}
{% set thumbnail = video_id and "https://img.youtube.com/vi/" + video_id + "/maxresdefault.jpg" %}
{% endif %}
<div class="common-card-style flex-column"> <div class="common-card-style flex-column">
<div class="course-image" style="background-image: url({{talk_doc.thumbnail}})"> <div class="course-image {% if not thumbnail %}default-image{% endif %}" {% if thumbnail %}
<div class="course-tags"></div> style="background-image: url( {{ thumbnail }} );" {% endif %}>
<div class="course-tags">
{% for tag in talk_doc.category.split(",") %}
<div class="course-card-pills">{{ tag }}</div>
{% endfor %}
</div>
{% if not thumbnail %}
<div class="default-image-text">{{ talk_doc.title[0] }}</div>
{% endif %}
</div> </div>
<div class="course-card-content"> <div class="course-card-content">
<div class="course-card-meta muted-text"> <div class="course-card-content"></div>
<span> {{talk_doc.category}} </span>
</div>
<div class="course-card-title">{{talk_doc.title}}</div> <div class="course-card-title">{{talk_doc.title}}</div>
<div class="muted-text mb-3">
{% if talk_doc.date %}
<span>
<img src="/assets/community/icons/calendar.svg">
{{ frappe.utils.format_date(talk_doc.date, "medium") }}
</span>
{% endif %}
<span class="pull-right">
{% if talk_doc.start_time %}
<span class="mr-3">
<b>From:</b>
{{ frappe.utils.format_time(talk_doc.start_time, "HH:mm") }}
</span>
{% endif %}
{% if talk_doc.end_time %}
<span>
<b>To:</b>
{{ frappe.utils.format_time(talk_doc.end_time, "HH:mm") }}
</span>
{% endif %}
</span>
</div>
<div class="card-divider"></div> <div class="card-divider"></div>
<div class="course-card-meta-2"> <div class="course-card-meta-2">
{{ widgets.Avatar(member=member, avatar_class="avatar-small")}}
{% set color = member.get_palette() %}
<a class="button-links" href="/user/{{member.username}}">
<span class="avatar avatar-small" title="{{ member.full_name }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
title="{{ member.full_name }}">
</img>
{% else %}
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
{{ frappe.utils.get_abbr(member.full_name) }}
</span>
{% endif %}
</span>
</a>
<span class="course-instructor"> {{ member.full_name }} </span> <span class="course-instructor"> {{ member.full_name }} </span>
<span class="small-title company-name"></span> <span class="small-title company-name"></span>
</div> </div>
@@ -49,5 +70,6 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@@ -85,7 +85,8 @@ web_include_css = "community.bundle.css"
# Override standard doctype classes # Override standard doctype classes
override_doctype_class = { override_doctype_class = {
"User": "community.overrides.user.CustomUser" "User": "community.overrides.user.CustomUser",
"Web Template": "community.overrides.web_template.CustomWebTemplate"
} }
# Document Events # Document Events
@@ -130,65 +131,28 @@ fixtures = ["Custom Field"]
# auto_cancel_exempted_doctypes = ["Auto Repeat"] # auto_cancel_exempted_doctypes = ["Auto Repeat"]
# Add all simple route rules here # Add all simple route rules here
primary_rules = [ website_route_rules = [
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"}, {"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
{"from_route": "/courses/<course>", "to_route": "courses/course"}, {"from_route": "/courses/<course>", "to_route": "courses/course"},
{"from_route": "/courses/<course>/<topic>", "to_route": "courses/topic"}, {"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"}, {"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"}, {"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"}, {"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},
{"from_route": "/courses/<course>/home", "to_route": "batch/home"}, {"from_route": "/courses/<course>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"}, {"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>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/schedule", "to_route": "batch/schedule"}, {"from_route": "/courses/<course>/schedule", "to_route": "batch/schedule"},
{"from_route": "/courses/<course>/members", "to_route": "batch/members"}, {"from_route": "/courses/<course>/members", "to_route": "batch/members"},
{"from_route": "/courses/<course>/discuss", "to_route": "batch/discuss"}, {"from_route": "/courses/<course>/discuss", "to_route": "batch/discuss"},
{"from_route": "/courses/<course>/about", "to_route": "batch/about"}, {"from_route": "/courses/<course>/about", "to_route": "batch/about"},
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"}, {"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/join", "to_route": "batch/join"}, {"from_route": "/courses/<course>/join", "to_route": "batch/join"},
{"from_route": "/discussions/<discussion>", "to_route": "discussions/discussion"}, {"from_route": "/discussions/<discussion>", "to_route": "discussions/discussion"},
{"from_route": "/user/<string(minlength=4):username>", "to_route": "profiles/profile"},
] ]
# Any frappe default URL is blocked by profile-rules, add it here to unblock it
whitelist = [
"/home",
"/login",
"/update-password",
"/update-profile",
"/third-party-apps",
"/website_script.js",
"/courses",
"/sketches",
"/admin",
"/socket.io",
"/hackathons",
"/dashboard",
"/join-request",
"/add-a-new-batch",
"/new-sign-up",
"/message",
"/about",
"/edit-profile",
"/attendee-registration",
"/speaker-registration",
"/event",
"/hello",
"/exhibitor-registration",
"/discussions",
"/propose-talk",
]
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]
# regex rule to match all profiles
profile_rules = [
{"from_route": "/<string(minlength=4):username>", "to_route": "profiles/profile"},
]
website_route_rules = primary_rules + whitelist_rules + profile_rules
website_redirects = [ website_redirects = [
{"source": "/update-profile", "target": "/edit-profile"}, {"source": "/update-profile", "target": "/edit-profile"},
] ]
update_website_context = 'community.widgets.update_website_context' update_website_context = 'community.widgets.update_website_context'
@@ -211,4 +175,5 @@ community_markdown_macro_renderers = {
"Exercise": "community.plugins.exercise_renderer", "Exercise": "community.plugins.exercise_renderer",
"Quiz": "community.plugins.quiz_renderer", "Quiz": "community.plugins.quiz_renderer",
"YouTubeVideo": "community.plugins.youtube_video_renderer", "YouTubeVideo": "community.plugins.youtube_video_renderer",
"Video": "community.plugins.video_renderer"
} }

View File

@@ -8,7 +8,9 @@
"field_order": [ "field_order": [
"course", "course",
"title", "title",
"column_break_3",
"description", "description",
"section_break_5",
"lessons" "lessons"
], ],
"fields": [ "fields": [
@@ -20,7 +22,7 @@
}, },
{ {
"fieldname": "description", "fieldname": "description",
"fieldtype": "Markdown Editor", "fieldtype": "Small Text",
"label": "Description" "label": "Description"
}, },
{ {
@@ -35,6 +37,14 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Lessons", "label": "Lessons",
"options": "Lessons" "options": "Lessons"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -45,7 +55,7 @@
"link_fieldname": "chapter" "link_fieldname": "chapter"
} }
], ],
"modified": "2021-07-27 16:28:08.667964", "modified": "2021-08-19 13:43:51.025072",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "Chapter", "name": "Chapter",

View File

@@ -3,11 +3,6 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from ..lesson.lesson import update_progress
class ExerciseSubmission(Document): class ExerciseSubmission(Document):
pass
def after_insert(self):
course_details = frappe.get_doc("LMS Course", self.course)
if not (course_details.is_mentor(frappe.session.user) or frappe.flags.in_test):
update_progress(self.lesson)

View File

@@ -17,6 +17,15 @@ frappe.ui.form.on('Lesson', {
</div> </div>
</div> </div>
<div class="row mb-3">
<div class="col-sm-4">
Video
</div>
<div class="col-sm-4">
{{ Video("url_of_source") }}
</div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-sm-4"> <div class="col-sm-4">
YouTube Video YouTube Video

View File

@@ -93,41 +93,3 @@ def save_progress(lesson, course, status):
}).save(ignore_permissions=True) }).save(ignore_permissions=True)
course_details = frappe.get_doc("LMS Course", course) course_details = frappe.get_doc("LMS Course", course)
return course_details.get_course_progress() return course_details.get_course_progress()
def update_progress(lesson):
user = frappe.session.user
if not all_dynamic_content_submitted(lesson, user):
return
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(ignore_permissions=True)
def all_dynamic_content_submitted(lesson, user):
all_exercises_submitted = check_all_exercise_submission(lesson, user)
all_quiz_submitted = check_all_quiz_submitted(lesson, user)
return all_exercises_submitted and all_quiz_submitted
def check_all_exercise_submission(lesson, user):
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, pluck="name", ignore_permissions=True)
if not len(exercise_names):
return True
query = {
"exercise": ["in", exercise_names],
"owner": user
}
if frappe.db.count("Exercise Submission", query) == len(exercise_names):
return True
return False
def check_all_quiz_submitted(lesson, user):
quizzes = frappe.get_list("LMS Quiz", {"lesson": lesson}, pluck="name", ignore_permissions=True)
if not len(quizzes):
return True
query = {
"quiz": ["in", quizzes],
"owner": user
}
if frappe.db.count("LMS Quiz Submission", query) == len(quizzes):
return True
return False

View File

@@ -35,14 +35,6 @@ class LMSBatch(Document):
filters['member_type'] = member_type filters['member_type'] = member_type
return frappe.db.exists("LMS Batch Membership", filters) return frappe.db.exists("LMS Batch Membership", filters)
def get_messages(self):
messages = frappe.get_all("LMS Message", {"batch": self.name}, ["*"], order_by="creation")
for message in messages:
message.message = frappe.utils.md_to_html(message.message)
if message.author == frappe.session.user:
message.author_name = "You"
message.is_author = True
return messages
def get_membership(self, email): def get_membership(self, email):
"""Returns the membership document of given user. """Returns the membership document of given user.

View File

@@ -29,13 +29,17 @@ def create_certificate(course):
return certificate return certificate
else: else:
expires_after_yrs = course_details.expiry expires_after_yrs = int(course_details.expiry)
expiry_date = None
if expires_after_yrs:
expiry_date = add_years(nowdate(), expires_after_yrs)
certificate = frappe.get_doc({ certificate = frappe.get_doc({
"doctype": "LMS Certification", "doctype": "LMS Certification",
"student": frappe.session.user, "student": frappe.session.user,
"course": course, "course": course,
"issue_date": nowdate(), "issue_date": nowdate(),
"expiry_date": add_years(nowdate(), int(expires_after_yrs)) "expiry_date": expiry_date
}) })
certificate.save(ignore_permissions=True) certificate.save(ignore_permissions=True)
return certificate.name return certificate.name

View File

@@ -4,6 +4,7 @@
frappe.ui.form.on('LMS Course', { frappe.ui.form.on('LMS Course', {
onload: function (frm) { onload: function (frm) {
frm.set_query("chapter", "chapters", function () { frm.set_query("chapter", "chapters", function () {
return { return {
filters: { filters: {
@@ -11,6 +12,14 @@ frappe.ui.form.on('LMS Course', {
} }
}; };
}); });
frm.set_query("instructor", function (doc) {
return {
filters: {
"ignore_user_type": 1,
}
};
});
} }
}); });

View File

@@ -18,9 +18,13 @@
"video_link", "video_link",
"image", "image",
"column_break_3", "column_break_3",
"instructor",
"tags", "tags",
"section_break_7",
"is_published", "is_published",
"column_break_9",
"upcoming", "upcoming",
"column_break_11",
"disable_self_learning", "disable_self_learning",
"section_break_5", "section_break_5",
"short_introduction", "short_introduction",
@@ -98,6 +102,27 @@
"label": "Chapters", "label": "Chapters",
"options": "Chapters" "options": "Chapters"
}, },
{
"fieldname": "instructor",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Instructor",
"options": "User"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Course Settings"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{ {
"fieldname": "certification_section", "fieldname": "certification_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -137,7 +162,7 @@
"link_fieldname": "course" "link_fieldname": "course"
} }
], ],
"modified": "2021-08-18 18:02:12.623807", "modified": "2021-08-25 11:04:57.211898",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Course", "name": "LMS Course",

View File

@@ -144,6 +144,8 @@ class LMSCourse(Document):
return batch_name and frappe.get_doc("LMS Batch", batch_name) return batch_name and frappe.get_doc("LMS Batch", batch_name)
def get_instructor(self): def get_instructor(self):
if self.instructor:
return frappe.get_doc("User", self.instructor)
return frappe.get_doc("User", self.owner) return frappe.get_doc("User", self.owner)
def get_chapters(self): def get_chapters(self):

View File

@@ -1,12 +1,10 @@
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from community.lms.doctype.lesson.lesson import update_progress
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
import json import json
from frappe import _ from frappe import _
from ..lesson.lesson import update_progress
class LMSQuiz(Document): class LMSQuiz(Document):
def validate(self): def validate(self):

View File

@@ -20,6 +20,7 @@ import markdown
from markdown import Extension from markdown import Extension
from markdown.inlinepatterns import InlineProcessor from markdown.inlinepatterns import InlineProcessor
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import html as HTML
def markdown_to_html(text): def markdown_to_html(text):
"""Renders markdown text into html. """Renders markdown text into html.
@@ -109,4 +110,5 @@ def sanitize_html(html, macro):
classname = "" classname = ""
if macro == "YouTubeVideo": if macro == "YouTubeVideo":
classname = "lesson-video" classname = "lesson-video"
return "<div class='" + classname + "'>" + "\n".join(str(node) for node in nodes) + "</div>" return "<div class='" + classname + "'>" + "\n".join(str(node) for node in nodes) + "</div>"

View File

@@ -14,15 +14,15 @@
</div> </div>
{% endif %} {% endif %}
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lessons"> <div class="lessons">
{% for lesson in course.get_lessons(chapter) %} {% for lesson in course.get_lessons(chapter) %}
<div class="lesson-info{% if membership.current_lesson == lesson.name %} active-lesson {% endif %}"> <div class="lesson-info{% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
{% if membership or lesson.include_in_preview %} {% if membership or lesson.include_in_preview or is_instructor %}
<a class="lesson-links" <a class="lesson-links" href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}"> data-course="{{ course.name }}">
{{ lesson.title }} {{ lesson.title }}
@@ -56,6 +56,11 @@
<script> <script>
frappe.ready(() => { frappe.ready(() => {
expand_the_active_chapter(); expand_the_active_chapter();
$(".chapter-title").click((e) => {
rotate_chapter_icon(e);
});
}) })
var expand_the_first_chapter = () => { var expand_the_first_chapter = () => {
@@ -97,4 +102,14 @@
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)"); $(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
} }
var rotate_chapter_icon = (e) => {
e.preventDefault();
var icon = $(e.currentTarget).children(".chapter-icon");
if (icon.css("transform") == "none") {
icon.css("transform", "rotate(90deg)");
} else {
icon.css("transform", "none");
}
}
</script> </script>

View File

@@ -0,0 +1,15 @@
import frappe
from frappe.website.doctype.web_template.web_template import WebTemplate
from community.widgets import Widgets
import json
class CustomWebTemplate(WebTemplate):
def render(self, values=None):
if not values:
values = {}
values = frappe.parse_json(values)
values.update({"values": values})
values.update({"widgets": Widgets()})
template = self.get_template(self.standard)
return frappe.render_template(template, values)

View File

@@ -8,3 +8,4 @@ community.patches.replace_member_with_user_in_lms_message
community.patches.replace_member_with_user_in_mentor_request community.patches.replace_member_with_user_in_mentor_request
community.patches.v0_0.chapter_lesson_index_table community.patches.v0_0.chapter_lesson_index_table
execute:frappe.delete_doc("DocType", "LMS Message") execute:frappe.delete_doc("DocType", "LMS Message")
community.patches.v0_0.course_instructor_update

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
frappe.reload_doc("lms", "doctype", "lms_course")
courses = frappe.get_all("LMS Course", fields=["name", "owner"])
for course in courses:
frappe.db.set_value("LMS Course", course.name, "instructor", course.owner)

View File

@@ -106,3 +106,6 @@ def youtube_video_renderer(video_id):
allowfullscreen> allowfullscreen>
</iframe> </iframe>
""" """
def video_renderer(src):
return "<video controls width='100%'><source src={0} type='video/mp4'></video>".format(src)

View File

@@ -630,6 +630,10 @@ input[type=checkbox] {
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.course-content-parent .chapter-description {
font-size: 0.7rem;
}
.chapter-icon { .chapter-icon {
margin-right: .25rem; margin-right: .25rem;
} }
@@ -1295,10 +1299,6 @@ pre {
font-weight: bold; font-weight: bold;
} }
a.talk-link {
text-decoration: none;
}
.speaker-cards-parent { .speaker-cards-parent {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
@@ -1414,17 +1414,24 @@ textarea.form-control {
} }
.certificate-page .common-card-style { .certificate-page .common-card-style {
flex-direction: column;
font-family: Inter; font-family: Inter;
color: black; color: black;
font-size: 2rem; width: 40%;
text-align: center; margin: 0 auto;
}
.certificate-content {
padding: 5rem; padding: 5rem;
background-image: url(/assets/community/images/certificate-background.png); }
.certificate-ribbon {
background-color: var(--primary-color);
margin-right: 2rem;
width: 20%;
} }
.certificate-heading { .certificate-heading {
font-size: 4rem; font-size: 2rem;
margin-bottom: 3rem; margin-bottom: 3rem;
font-weight: bold; font-weight: bold;
} }
@@ -1433,18 +1440,47 @@ textarea.form-control {
margin-bottom: 3rem; margin-bottom: 3rem;
} }
@media (max-width: 768px) { .certificate-logo {
.certificate-page .common-card-style { height: 20px;
padding: 2rem; margin-top: 2rem;
font-size: 1.5rem; }
}
.certificate-heading { @media (max-width: 1024px) {
font-size: 3rem; .certificate-content {
padding: 3rem;
} }
} }
@media (max-width: 360px) {
@media (max-width: 768px) {
.certificate-page .common-card-style {
width: 50%;
}
.certificate-content {
padding: 2rem;
}
.certificate-ribbon {
margin-right: 1rem;
}
.certificate-heading {
font-size: 2rem;
}
}
@media (max-width: 550px) {
.certificate-page .common-card-style {
width: 60%;
}
}
@media (max-width: 500px) {
.certificate-page .common-card-style {
width: 90%;
}
.certificate-heading { .certificate-heading {
font-size: 2rem; font-size: 2rem;
} }

View File

@@ -1,32 +1,35 @@
<div class="common-card-style"> <div class="common-card-style">
<div class="certificate-heading"> <div class="certificate-content">
Certificate of Completion <div class="certificate-heading">
</div> Certificate of Completion
<div class="certificate-para">
This is to certify that <span class="font-weight-bold">{{ student.full_name }}</span> has successfully completed
<span class="font-weight-bold">{{ course.title }}</span> online course on
<span class="font-weight-bold">{{ frappe.utils.format_date(certificate.issue_date, "medium") }}</span>
</div>
<div style="display: flex; justify-content: space-between;" class="certificate-footer">
<div>
<div class="font-weight-bold">
Instructor:
</div>
<div>
{{ instructor.full_name }}
</div>
</div> </div>
<div> <div class="certificate-para">
<div class="font-weight-bold"> This is to certify that <span class="font-weight-bold">{{ student.full_name }}</span> has successfully completed
Expiry Date: <span class="font-weight-bold">{{ course.title }}</span> online course on
</div> <span class="font-weight-bold">{{ frappe.utils.format_date(certificate.issue_date, "medium") }}</span>
<div>
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
</div>
</div> </div>
<div class="certificate-footer">
<div>
<span>
Instructor:
</span>
<span class="font-weight-bold">
{{ instructor.full_name }}
</span>
</div>
{% if certificate.expiry_date %}
<div>
<span>
Expiry Date:
</span>
<span class="font-weight-bold">
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
</span>
</div>
{% endif %}
</div>
<img src="{{ logo }}" class="certificate-logo">
</div> </div>
<div> <div class="certificate-ribbon"></div>
<img src="{{ logo }}" style="height: 50px;">
</div>
</div> </div>
<script src="/assets/community/js/html2canvas.js"></script> <script src="/assets/community/js/html2canvas.js"></script>

View File

@@ -26,10 +26,9 @@
<div class="custom-checkbox"> <div class="custom-checkbox">
<label class="quiz-label"> <label class="quiz-label">
<input class="option" value="{{ option | urlencode }}" <input class="option" value="{{ option | urlencode }}"
data-correct="{{ question['is_correct_' + loop.index | string] }}" data-correct="{{ question['is_correct_' + loop.index | string] }}" {% if question.multiple %}
{% if question.multiple %} type="checkbox" type="checkbox" {% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
{% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}> <img class="empty-checkbox mr-3" />
<img class="empty-checkbox mr-3"/>
</label> </label>
<span class="label-area">{{ frappe.utils.md_to_html(option) }}</span> <span class="label-area">{{ frappe.utils.md_to_html(option) }}</span>
</div> </div>
@@ -51,6 +50,7 @@
<button class="btn btn-primary pull-right" id="check" disabled>Check</button> <button class="btn btn-primary pull-right" id="check" disabled>Check</button>
<button class="btn btn-primary hide" id="next">Next</button> <button class="btn btn-primary hide" id="next">Next</button>
<button class="btn btn-primary hide" id="summary">Summary</button> <button class="btn btn-primary hide" id="summary">Summary</button>
<small id="submission-message" class="font-weight-bold hide"> Please join the course to submit the Quiz.</small>
</div> </div>
<div class="button is-secondary pull-right hide" id="try-again">Try Again</div> <div class="button is-secondary pull-right hide" id="try-again">Try Again</div>
<h4 class="success-message"></h4> <h4 class="success-message"></h4>

View File

@@ -29,8 +29,9 @@
{% endif %} {% endif %}
{% set title = lesson.title + " - " + course.title %} {% set title = lesson.title + " - " + course.title %}
{% set condition = membership or is_instructor %}
{{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name, {{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name,
title=title, condition=membership, button_name="Start Learning", title=title, condition=condition, button_name="Start Learning",
redirect_to="/courses/" + course.name) }} redirect_to="/courses/" + course.name) }}
</div> </div>
</div> </div>
@@ -39,14 +40,18 @@
{% endblock %} {% endblock %}
{% macro LessonContent(lesson) %} {% macro LessonContent(lesson) %}
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lesson-content"> <div class="lesson-content">
<div class="course-home-headings title {% if membership %} is-member {% endif %}" data-lesson="{{ lesson.name }}" <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 }}"> data-course="{{ course.name }}">
{{ lesson.title }} {{ lesson.title }}
<span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span> <span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span>
</div> </div>
{% if membership or lesson.include_in_preview %}
{% if membership or lesson.include_in_preview or is_instructor %}
<div class="common-card-style lesson-content-card markdown-source">{{ lesson.render_html() }}</div> <div class="common-card-style lesson-content-card markdown-source">{{ lesson.render_html() }}</div>
{% else %} {% else %}
<div class="common-card-style lesson-content-card"> <div class="common-card-style lesson-content-card">

View File

@@ -133,7 +133,18 @@ var check_answer = (e) => {
$(".explanation").removeClass("hide"); $(".explanation").removeClass("hide");
$("#check").addClass("hide"); $("#check").addClass("hide");
current_index == total_questions ? $("#summary").removeClass("hide") : $("#next").removeClass("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(); var [answer, is_correct] = parse_options();
add_to_local_storage(quiz_name, current_index, answer, is_correct) add_to_local_storage(quiz_name, current_index, answer, is_correct)

View File

@@ -1,13 +1,9 @@
frappe.ready(() => { frappe.ready(() => {
if ($(document).width() <= 550) {
$(".certificate-footer").css("flex-direction", "column");
$(".certificate-footer").children().addClass("mb-5");
}
$("#export-as-pdf").click((e) => { $("#export-as-pdf").click((e) => {
export_as_pdf(e); export_as_pdf(e);
}) })
}) })
var export_as_pdf = (e) => { var export_as_pdf = (e) => {

View File

@@ -16,9 +16,9 @@ def get_context(context):
redirect_to_course_list() redirect_to_course_list()
context.course = frappe.db.get_value("LMS Course", course_name, context.course = frappe.db.get_value("LMS Course", course_name,
["owner", "title", "name"], as_dict=True) ["instructor", "title", "name"], as_dict=True)
context.instructor = frappe.db.get_value("User", context.course.owner, context.instructor = frappe.db.get_value("User", context.course.instructor,
["full_name", "username"], as_dict=True) ["full_name", "username"], as_dict=True)
context.student = frappe.db.get_value("User", context.certificate.student, context.student = frappe.db.get_value("User", context.certificate.student,

View File

@@ -43,7 +43,7 @@
</div> </div>
</div> </div>
<div class="course-buttons"> <div class="course-buttons">
{% if not course.disable_self_learning and not membership %} {% 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 }}"> <div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
Start Learning Start Learning
<img class="ml-2" src="/assets/community/icons/white-arrow.svg" /> <img class="ml-2" src="/assets/community/icons/white-arrow.svg" />

View File

@@ -29,10 +29,6 @@ frappe.ready(() => {
show_review_dialog(e); show_review_dialog(e);
}); });
$(".chapter-title").click((e) => {
rotate_chapter_icon(e);
});
$(".icon-rating").click((e) => { $(".icon-rating").click((e) => {
highlight_rating(e); highlight_rating(e);
}); });
@@ -168,16 +164,6 @@ var show_review_dialog = (e) => {
$("#review-modal").modal("show"); $("#review-modal").modal("show");
} }
var rotate_chapter_icon = (e) => {
e.preventDefault();
var icon = $(e.currentTarget).children(".chapter-icon");
if (icon.css("transform") == "none") {
icon.css("transform", "rotate(90deg)");
} else {
icon.css("transform", "none");
}
}
var highlight_rating = (e) => { var highlight_rating = (e) => {
var rating = $(e.currentTarget).attr("data-rating"); var rating = $(e.currentTarget).attr("data-rating");
$(".icon-rating").removeClass("star-click"); $(".icon-rating").removeClass("star-click");

View File

View File

@@ -59,6 +59,9 @@
{% endmacro %} {% endmacro %}
{% macro AboutOverviewSection(member) %} {% 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"> <div class="profile-parent-section">
{% if member.bio %} {% if member.bio %}
<div class="profile-about-section"> <div class="profile-about-section">
@@ -70,31 +73,35 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if enrollment or reviews or mentorship %}
<div class="course-overview-section"> <div class="course-overview-section">
<div class="course-home-headings"> <div class="course-home-headings">
Overview Overview
</div> </div>
<div class="common-card-style overview-card small-title"> <div class="common-card-style overview-card small-title">
{% if member.get_course_membership("Student") | length %} {% if enrollment %}
<div class="overtime-item"> <div class="overtime-item">
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" /> <img class="icon-background mr-1" src="/assets/community/icons/user.svg" />
{{ member.get_course_membership("Student") | length }} Enrolled {{ enrollment }} Enrolled
</div> </div>
{% endif %} {% endif %}
{% if member.get_user_reviews() | length %} {% if reviews %}
<div class="overtime-item"> <div class="overtime-item">
<img class="icon-background mr-1" src="/assets/community/icons/rating.svg" /> <img class="icon-background mr-1" src="/assets/community/icons/rating.svg" />
{{ member.get_user_reviews() | length }} Created {{ reviews }} Created
</div> </div>
{% endif %} {% endif %}
{% if member.get_course_membership("Mentor") | length%} {% if mentorship %}
<div class="overtime-item"> <div class="overtime-item">
<img class="icon-background mr-1" src="/assets/community/icons/calendar.svg" /> <img class="icon-background mr-1" src="/assets/community/icons/calendar.svg" />
{{ member.get_course_membership("Mentor") | length }} Mentored {{ mentorship }} Mentored
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %}
</div> </div>
{% endmacro %} {% endmacro %}