Merge branch 'main' of https://github.com/frappe/community into community-member-to-user-refactor
This commit is contained in:
@@ -24,6 +24,8 @@ class Exercise(Document):
|
||||
},
|
||||
order_by="creation desc",
|
||||
page_length=1)
|
||||
|
||||
print("get_user_submission", result)
|
||||
if result:
|
||||
return result[0]
|
||||
|
||||
@@ -41,6 +43,8 @@ class Exercise(Document):
|
||||
course = frappe.get_doc("LMS Course", self.course)
|
||||
batch = course.get_student_batch(user)
|
||||
|
||||
image = livecode_to_svg(None, code)
|
||||
|
||||
doc = frappe.get_doc(
|
||||
doctype="Exercise Submission",
|
||||
exercise=self.name,
|
||||
@@ -48,7 +52,8 @@ class Exercise(Document):
|
||||
course=self.course,
|
||||
lesson=self.lesson,
|
||||
batch=batch and batch.name,
|
||||
image=image,
|
||||
solution=code)
|
||||
doc.insert()
|
||||
doc.insert(ignore_permissions=True)
|
||||
return doc
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"exercise_title",
|
||||
"course",
|
||||
"batch",
|
||||
"lesson"
|
||||
"lesson",
|
||||
"image"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -54,11 +55,17 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Lesson",
|
||||
"options": "Lesson"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Code",
|
||||
"label": "Image",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-20 13:30:16.349278",
|
||||
"modified": "2021-05-21 11:28:45.833018",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Exercise Submission",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from ..lms_topic.section_parser import SectionParser
|
||||
from ...section_parser import SectionParser
|
||||
|
||||
class Lesson(Document):
|
||||
def before_save(self):
|
||||
|
||||
@@ -37,13 +37,37 @@ class LMSBatch(Document):
|
||||
member_names = [m['member'] for m in memberships]
|
||||
return find_all("User", name=["IN", member_names])
|
||||
|
||||
def is_member(self, email):
|
||||
def is_member(self, email, member_type=None):
|
||||
"""Checks if a person is part of a batch.
|
||||
|
||||
If member_type is specified, checks if the person is a Student/Mentor.
|
||||
"""
|
||||
member = find("Community Member", email=email)
|
||||
return member and frappe.db.exists(
|
||||
if not member:
|
||||
return
|
||||
|
||||
filters = {
|
||||
"batch": self.name,
|
||||
"member": member.name
|
||||
}
|
||||
if member_type:
|
||||
filters['member_type'] = member_type
|
||||
return frappe.db.exists("LMS Batch Membership", filters)
|
||||
|
||||
def get_students(self):
|
||||
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
|
||||
"""
|
||||
memberships = frappe.get_all(
|
||||
"LMS Batch Membership",
|
||||
{"batch": self.name, "member": member.name})
|
||||
{"batch": self.name, "member_type": "Student"},
|
||||
["member"])
|
||||
member_names = [m['member'] for m in memberships]
|
||||
members = frappe.get_all(
|
||||
"Community Member",
|
||||
{"name": ["IN", member_names]},
|
||||
["email", "full_name", "username"])
|
||||
return members
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_messages(batch):
|
||||
|
||||
@@ -35,17 +35,6 @@ class LMSCourse(Document):
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name}>"
|
||||
|
||||
def get_topic(self, slug):
|
||||
"""Returns the topic with given slug in this course as a Document.
|
||||
"""
|
||||
result = frappe.get_all(
|
||||
"LMS Topic",
|
||||
filters={"course": self.name, "slug": slug})
|
||||
|
||||
if result:
|
||||
row = result[0]
|
||||
return frappe.get_doc('LMS Topic', row['name'])
|
||||
|
||||
def has_mentor(self, email):
|
||||
"""Checks if this course has a mentor with given email.
|
||||
"""
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2021, FOSS United and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('LMS Topic', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"creation": "2021-03-02 07:20:41.686573",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"course",
|
||||
"title",
|
||||
"slug",
|
||||
"preview",
|
||||
"description",
|
||||
"order",
|
||||
"sections"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "order",
|
||||
"fieldtype": "Int",
|
||||
"label": "Order"
|
||||
},
|
||||
{
|
||||
"fieldname": "preview",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Preview"
|
||||
},
|
||||
{
|
||||
"fieldname": "sections",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sections",
|
||||
"options": "LMS Section"
|
||||
},
|
||||
{
|
||||
"description": "The slug of the topic. Autogenerated from the title if not specified.",
|
||||
"fieldname": "slug",
|
||||
"fieldtype": "Data",
|
||||
"label": "Slug"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-06 14:12:48.514062",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Topic",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "title",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1,
|
||||
"track_views": 1
|
||||
}
|
||||
@@ -1,43 +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
|
||||
from .section_parser import SectionParser
|
||||
from ...utils import slugify
|
||||
|
||||
class LMSTopic(Document):
|
||||
def before_save(self):
|
||||
course = self.get_course()
|
||||
if not self.slug:
|
||||
self.slug = self.generate_slug(title=self.title)
|
||||
|
||||
sections = SectionParser().parse(self.description or "")
|
||||
self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)]
|
||||
|
||||
def get_course(self):
|
||||
return frappe.get_doc("LMS Course", self.course)
|
||||
|
||||
def generate_slug(self, title):
|
||||
result = frappe.get_all(
|
||||
'LMS Topic',
|
||||
filters={'course': self.course},
|
||||
fields=['slug'])
|
||||
slugs = set([row['slug'] for row in result])
|
||||
return slugify(title, used_slugs=slugs)
|
||||
|
||||
def get_sections(self):
|
||||
return sorted(self.sections, key=lambda s: s.index)
|
||||
|
||||
def make_lms_section(self, index, section):
|
||||
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
|
||||
s.type = section.type
|
||||
s.label = section.label
|
||||
s.contents = section.contents
|
||||
s.index = index
|
||||
return s
|
||||
|
||||
|
||||
|
||||
@@ -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})>'
|
||||
@@ -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 TestLMSTopic(unittest.TestCase):
|
||||
pass
|
||||
Reference in New Issue
Block a user