feat: course enrollment and my courses page
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Community Course Enrollment', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-03-03 11:24:08.220185",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"course"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "course",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Course",
|
||||||
|
"options": "Community Course"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-03-03 11:44:32.011805",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Community",
|
||||||
|
"name": "Community Course Enrollment",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Student",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class CommunityCourseEnrollment(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestCommunityCourseEnrollment(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -41,7 +41,8 @@
|
|||||||
"fieldname": "email",
|
"fieldname": "email",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Email",
|
"label": "Email",
|
||||||
"options": "Email"
|
"options": "Email",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "short_intro",
|
"fieldname": "short_intro",
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_published_field": "enabled",
|
"is_published_field": "enabled",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-02 17:53:40.252130",
|
"modified": "2021-03-03 15:00:46.298535",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Community",
|
"module": "Community",
|
||||||
"name": "Community Course Member",
|
"name": "Community Course Member",
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"route": "username",
|
"route": "me",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ from frappe.website.website_generator import WebsiteGenerator
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
class CommunityCourseMember(WebsiteGenerator):
|
class CommunityCourseMember(WebsiteGenerator):
|
||||||
|
|
||||||
|
def get_context(self, context):
|
||||||
|
context.abbr = ("").join([ s[0] for s in self.full_name.split() ])
|
||||||
|
return context
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_user_name()
|
self.validate_user_name()
|
||||||
if not self.route:
|
if not self.route:
|
||||||
self.route = "username/" + self.user_name
|
self.route = "me/" + self.user_name
|
||||||
|
|
||||||
def validate_user_name(self):
|
def validate_user_name(self):
|
||||||
if len(self.user_name) < 4:
|
if len(self.user_name) < 4:
|
||||||
@@ -55,7 +60,7 @@ class CommunityCourseMember(WebsiteGenerator):
|
|||||||
"username": self.user_name,
|
"username": self.user_name,
|
||||||
"send_welcome_email": 0,
|
"send_welcome_email": 0,
|
||||||
"user_type": 'Website User',
|
"user_type": 'Website User',
|
||||||
"redirect_url": "username/" + self.name
|
"redirect_url": "me/" + self.name
|
||||||
})
|
})
|
||||||
user.save(ignore_permissions=True)
|
user.save(ignore_permissions=True)
|
||||||
update_password_link = user.reset_password()
|
update_password_link = user.reset_password()
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
{% extends "templates/web.html" %}
|
{% extends "templates/web.html" %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="py-20 row">
|
<div class="py-20 row">
|
||||||
|
{% if photo %}
|
||||||
<div class="col-sm-2 border border-dark">
|
<div class="col-sm-2 border border-dark">
|
||||||
<img src="{{ photo }}" alt="{{ full_name }}">
|
<img src="{{ photo }}" alt="{{ full_name }}">
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<div class="standard-image" style="font-size: 30px;">{{ abbr }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>{{ full_name }}</h1>
|
<h1>{{ full_name }}</h1>
|
||||||
|
{% if short_intro %}
|
||||||
<p class="lead"> {{ short_intro }} </p>
|
<p class="lead"> {{ short_intro }} </p>
|
||||||
|
{% endif %}
|
||||||
|
{% if bio %}
|
||||||
<p class="markdown-style"> {{ frappe.utils.md_to_html(bio) }} </p>
|
<p class="markdown-style"> {{ frappe.utils.md_to_html(bio) }} </p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"login_required": 0,
|
"login_required": 0,
|
||||||
"max_attachment_size": 0,
|
"max_attachment_size": 0,
|
||||||
"modified": "2021-03-02 11:31:39.072501",
|
"modified": "2021-03-03 11:02:33.907687",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Community",
|
"module": "Community",
|
||||||
"name": "community-course-membership",
|
"name": "community-course-membership",
|
||||||
@@ -70,42 +70,6 @@
|
|||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"show_in_filter": 0
|
"show_in_filter": 0
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_read_on_all_link_options": 0,
|
|
||||||
"fieldname": "short_intro",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"label": "Short Intro",
|
|
||||||
"max_length": 0,
|
|
||||||
"max_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"show_in_filter": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_read_on_all_link_options": 0,
|
|
||||||
"fieldname": "bio",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"label": "Bio",
|
|
||||||
"max_length": 0,
|
|
||||||
"max_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"show_in_filter": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_read_on_all_link_options": 0,
|
|
||||||
"fieldname": "photo",
|
|
||||||
"fieldtype": "Attach Image",
|
|
||||||
"hidden": 0,
|
|
||||||
"label": "Photo",
|
|
||||||
"max_length": 0,
|
|
||||||
"max_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"show_in_filter": 0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,18 @@ from . import __version__ as app_version
|
|||||||
|
|
||||||
app_name = "community"
|
app_name = "community"
|
||||||
app_title = "Community"
|
app_title = "Community"
|
||||||
app_publisher = "Frappe"
|
app_publisher = "FOSS United"
|
||||||
app_description = "Community App"
|
app_description = "Community App"
|
||||||
app_icon = "octicon octicon-file-directory"
|
app_icon = "octicon octicon-file-directory"
|
||||||
app_color = "grey"
|
app_color = "grey"
|
||||||
app_email = "jannat@erpnext.com"
|
app_email = "jannat@erpnext.com"
|
||||||
app_license = "MIT"
|
app_license = "AGPL"
|
||||||
|
|
||||||
# Includes in <head>
|
# Includes in <head>
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
# include js, css files in header of desk.html
|
# include js, css files in header of desk.html
|
||||||
# app_include_css = "/assets/community/css/community.css"
|
app_include_css = "/assets/community/css/community.css"
|
||||||
# app_include_js = "/assets/community/js/community.js"
|
app_include_js = "/assets/community/js/community.js"
|
||||||
|
|
||||||
# include js, css files in header of web template
|
# include js, css files in header of web template
|
||||||
# web_include_css = "/assets/community/css/community.css"
|
# web_include_css = "/assets/community/css/community.css"
|
||||||
|
|||||||
@@ -16,9 +16,19 @@
|
|||||||
<li class="breadcrumb-item" aria-current="page"><a href="/courses">Courses</a></li>
|
<li class="breadcrumb-item" aria-current="page"><a href="/courses">Courses</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
{% if course_enrolled %}
|
||||||
|
<p>
|
||||||
|
<div class="badge badge-info">Enrolled</div>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% if not course_enrolled %}
|
||||||
|
<button class="btn btn-dark btn-enroll float-right" data-course={{course.name}}>Enroll</button>
|
||||||
|
{% endif %}
|
||||||
<h1>{{ course.title }}</h1>
|
<h1>{{ course.title }}</h1>
|
||||||
|
</div>
|
||||||
|
<div>{{ frappe.utils.md_to_html(course.description) }}</div>
|
||||||
|
|
||||||
<div>{{ course.description }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
|
|||||||
17
community/www/courses/course.js
Normal file
17
community/www/courses/course.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* frappe.ready(() => {
|
||||||
|
var url_params = new URLSearchParams(window.location.search);
|
||||||
|
frappe.call('community.www.courses.course.has_enrolled', { course: url_params.get("course") }, (data) => {
|
||||||
|
if (data.message) {
|
||||||
|
$(".btn-enroll").addClass("hide");
|
||||||
|
$(".enrollment-details").removeClass("hide");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}) */
|
||||||
|
|
||||||
|
$('.btn-enroll').on('click', (e) => {
|
||||||
|
frappe.call('community.www.courses.course.enroll', { course: $(e.target).attr("data-course") }, (data) => {
|
||||||
|
$(".btn-enroll").addClass("hide");
|
||||||
|
$(".enrollment-details").removeClass("hide");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ def get_context(context):
|
|||||||
frappe.local.flags.redirect_location = '/courses'
|
frappe.local.flags.redirect_location = '/courses'
|
||||||
raise frappe.Redirect
|
raise frappe.Redirect
|
||||||
context.course = get_course(course_id)
|
context.course = get_course(course_id)
|
||||||
|
context.course_enrolled = has_enrolled(course_id)
|
||||||
|
|
||||||
def get_course(name):
|
def get_course(name):
|
||||||
course = frappe.db.get_value('Community Course', name,
|
course = frappe.db.get_value('Community Course', name,
|
||||||
@@ -19,5 +20,16 @@ def get_course(name):
|
|||||||
fields=['name', 'title', 'description'],
|
fields=['name', 'title', 'description'],
|
||||||
order_by='creation'
|
order_by='creation'
|
||||||
)
|
)
|
||||||
print(course)
|
|
||||||
return course
|
return course
|
||||||
|
|
||||||
|
def has_enrolled(course):
|
||||||
|
return frappe.db.get_value("Community Course Enrollment", {"course": course, "owner": frappe.session.user})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def enroll(course):
|
||||||
|
return frappe.get_doc({
|
||||||
|
"doctype": "Community Course Enrollment",
|
||||||
|
"course": course
|
||||||
|
}).save()
|
||||||
|
|
||||||
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
<div class="card mb-5 w-100">
|
<div class="card mb-5 w-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title"><a href="/courses/course?course={{course.name}}">{{course.title}}</a></h5>
|
<h5 class="card-title"><a href="/courses/course?course={{course.name}}">{{course.title}}</a></h5>
|
||||||
<p class="card-text">{{course.description}}</p>
|
<p class="card-text">{{ frappe.utils.md_to_html(course.description[:250]) }}</p>
|
||||||
<a href="/courses/course?id={{course.name}}" class="card-link">See more →</a>
|
<a href="/courses/course?course={{course.name}}" class="card-link">See more →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<h1>jannat</h1>
|
|
||||||
67
community/www/my-courses/index.html
Normal file
67
community/www/my-courses/index.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{% extends "templates/base.html" %}
|
||||||
|
{% from "www/hackathons/macros/card.html" import null_card %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'My Courses' }}{% endblock %}
|
||||||
|
{% block head_include %}
|
||||||
|
<meta name="description" content="My Courses" />
|
||||||
|
<meta name="keywords" content="" />
|
||||||
|
<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(course) %}
|
||||||
|
<div class="col-sm-4 mb-4 text-left">
|
||||||
|
<a href="//courses/course?course={{course.name}}" class="no-decoration no-underline">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class='card-body'>
|
||||||
|
<h5 class='card-title'>{{ course.title }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section">
|
||||||
|
<div class='container'>
|
||||||
|
<div class="row mt-5">
|
||||||
|
{% for course in my_courses %}
|
||||||
|
{{ card(course) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% if my_courses %}
|
||||||
|
{% for n in range( (3 - (my_courses|length)) %3) %}
|
||||||
|
{{ null_card() }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
15
community/www/my-courses/index.py
Normal file
15
community/www/my-courses/index.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
context.no_cache = 1
|
||||||
|
context.my_courses = get_my_courses()
|
||||||
|
|
||||||
|
def get_my_courses():
|
||||||
|
my_courses = []
|
||||||
|
courses = frappe.get_all("Community Course Enrollment", {"owner": frappe.session.user}, ["course"])
|
||||||
|
for course in courses:
|
||||||
|
my_courses.append({
|
||||||
|
"name": course.course,
|
||||||
|
"title": frappe.db.get_value("Community Course", course.course, ["title"])
|
||||||
|
})
|
||||||
|
return my_courses
|
||||||
Reference in New Issue
Block a user