Skip to content

Note

note

NOTE

Handles notes.

Source code in modules/note.py
class NOTE:
    """Handles notes.
    """
    def __init__(self, Books):
        self.Books = Books

    def authUpload(self):
        auth = current.auth
        db = current.db

        myId = None
        authorized = False
        if auth.user:
            myId = auth.user.id
        if myId:
            sql = dedent(
                f"""
                select uid from uploaders where uid = {myId}
                """
            )
            records = db.executesql(sql)
            authorized = (
                records is not None and len(records) == 1 and records[0][0] == myId
            )
        msg = "" if authorized else "you are not allowed to upload notes as csv files"
        return (authorized, myId, msg)

    def page(self, ViewSettings):
        Check = current.Check

        pageConfig = ViewSettings.writeConfig()

        key_id = Check.isId("goto", "n", "note", [])

        (authorized, myId, msg) = self.authUpload()
        return dict(
            pageConfig=pageConfig,
            key_id=key_id,
            mayUpload=authorized,
            user_id=myId,
        )

    def getVerseNotes(self):
        """Get the notes belonging to a single verse.

        Reads request parameters to determine which verse.
        """
        Check = current.Check
        auth = current.auth

        vr = Check.field("material", "", "version")
        bk = Check.field("material", "", "book")
        ch = Check.field("material", "", "chapter")
        vs = Check.field("material", "", "verse")
        edit = Check.isBool("edit")

        myId = None
        if auth.user:
            myId = auth.user.id
        authenticated = myId is not None

        clauseAtoms = self.getClauseAtoms(vr, bk, ch, vs)
        changed = False

        msgs = []

        return self.inVerse(
            vr, bk, ch, vs, myId, clauseAtoms, changed, msgs, authenticated, edit
        )

    def body(self):
        """Retrieves a note set record based on parameters.
        """
        Check = current.Check

        vr = Check.field("material", "", "version")
        iidRep = Check.field("material", "", "iid")

        (iid, keywords) = iDecode("n", iidRep)
        msgs = []
        if not iid:
            msg = f"Not a valid note id: {iid}"
            msgs.append(("error", msg))
            return dict(
                noteRecord=dict(),
                note=json.dumps(dict()),
                msgs=json.dumps(msgs),
            )
        noteRecord = self.getInfo(iidRep, vr, msgs)
        return dict(
            vr=vr,
            noteRecord=noteRecord,
            note=json.dumps(noteRecord),
            msgs=json.dumps(msgs),
        )

    def getItems(self, vr, book, chapter, is_published):
        NOTE_DB = current.NOTE_DB

        bk = book["name"]
        ch = chapter["chapter_num"]
        if is_published == "x":
            isPubVersion = ""
        else:
            isPubVersion = " and is_published = 'T'"
        sql = dedent(
            f"""
            select
                note.created_by,
                shebanq_web.auth_user.first_name,
                shebanq_web.auth_user.last_name,
                note.keywords,
                note.verse,
                note.is_published
            from note
            inner join shebanq_web.auth_user
            on shebanq_web.auth_user.id = created_by
            where
                version = '{vr}' and
                book = '{bk}' and chapter = {ch} {isPubVersion}
            order by note.verse
            ;
            """
        )
        records = NOTE_DB.executesql(sql)
        user = {}
        nPublished = collections.Counter()
        nNotes = collections.Counter()
        nVerses = {}
        for (user_id, first_name, last_name, keywords, v, is_published) in records:
            if user_id not in user:
                user[user_id] = (first_name, last_name)
            for keyword in set(keywords.strip().split()):
                if is_published == "T":
                    nPublished[(user_id, keyword)] += 1
                nNotes[(user_id, keyword)] += 1
                nVerses.setdefault((user_id, keyword), set()).add(v)
        r = []
        for (user_id, keyword) in nNotes:
            (first_name, last_name) = user[user_id]
            thisNPub = nPublished[(user_id, keyword)]
            thisNNotes = nNotes[(user_id, keyword)]
            thisNVerses = len(nVerses[(user_id, keyword)])
            r.append(
                {
                    "item": dict(
                        id=iEncode("n", user_id, keyword),
                        first_name=first_name,
                        last_name=last_name,
                        keywords=keyword,
                        is_published=thisNPub > 0,
                        nNotes=thisNNotes,
                        nVerses=thisNVerses,
                    ),
                    "slots": json.dumps([]),
                }
            )
        return r

    def read(self, vr, iid, keywords):
        auth = current.auth
        NOTE_DB = current.NOTE_DB

        clauseAtomFirst = self.getClauseAtomFirstSlot(vr)
        keywordsSql = keywords.replace("'", "''")
        myId = auth.user.id if auth.user is not None else None
        extra = "" if myId is None else f" or created_by = {myId} "
        sql = dedent(
            f"""
            select book, clause_atom from note
            where keywords like '% {keywordsSql} %'
            and version = '{vr}' and (is_shared = 'T' {extra})
            ;
            """
        )
        clauseAtoms = NOTE_DB.executesql(sql)
        slots = {clauseAtomFirst[x[0]][x[1]] for x in clauseAtoms}
        return normRanges(None, fromset=slots)

    def getClauseAtoms(self, vr, bk, ch, vs):
        Caching = current.Caching
        return Caching.get(
            f"clause_atoms_{vr}_{bk}_{ch}_{vs}_",
            lambda: self.getClauseAtoms_c(vr, bk, ch, vs),
            ALWAYS,
        )

    def getClauseAtoms_c(self, vr, bk, ch, vs):
        PASSAGE_DBS = current.PASSAGE_DBS

        clauseAtoms = []
        caData = (
            PASSAGE_DBS[vr].executesql(
                dedent(
                    f"""
                    select
                       distinct word.clause_atom_number
                    from
                        verse
                    inner join
                        word_verse on verse.id = word_verse.verse_id
                    inner join
                        word on word.word_number = word_verse.anchor
                    inner join
                        chapter on chapter.id = verse.chapter_id
                    inner join
                        book on book.id = chapter.book_id
                    where
                        book.name = '{bk}' and
                        chapter.chapter_num = {ch} and
                        verse.verse_num = {vs}
                    order by
                        word.clause_atom_number
                    ;
                    """
                )
            )
            if vr in PASSAGE_DBS
            else []
        )

        for row in caData:
            clauseAtoms.append(row[0])
        return clauseAtoms

    def getClauseAtomFirstSlot(self, vr):
        Caching = current.Caching

        return Caching.get(
            f"clause_atom_f_{vr}_",
            lambda: self.getClauseAtomFirstSlot_c(vr),
            ALWAYS,
        )

    def getClauseAtomFirstSlot_c(self, vr):
        Books = self.Books
        PASSAGE_DBS = current.PASSAGE_DBS

        (books, booksOrder, bookIds, bookName) = Books.get(vr)
        sql = dedent(
            """
            select book_id, ca_num, first_m
            from clause_atom
            ;
            """
        )
        caData = PASSAGE_DBS[vr].executesql(sql) if vr in PASSAGE_DBS else []
        caFirst = {}
        for (book_id, ca_num, first_m) in caData:
            book_name = bookName[book_id]
            caFirst.setdefault(book_name, {})[ca_num] = first_m
        return caFirst

    def getInfo(self, iidRep, vr, msgs):
        db = current.db

        (iid, keywords) = iDecode("n", iidRep)
        if iid is None:
            return {}
        nRecord = dict(
            id=iidRep,
            user_id=iid,
            first_name="N?",
            last_name="N?",
            keywords=keywords,
            versions={},
        )
        nRecord["versions"] = self.countNotes(iid, keywords)
        sql = dedent(
            f"""
            select first_name, last_name from auth_user where id = '{iid}'
            ;
            """
        )
        uinfo = db.executesql(sql)
        if uinfo is not None and len(uinfo) > 0:
            nRecord["first_name"] = uinfo[0][0]
            nRecord["last_name"] = uinfo[0][1]
        return nRecord

    def countNotes(self, user_id, keywords):
        auth = current.auth
        NOTE_DB = current.NOTE_DB

        keywordsSql = keywords.replace("'", "''")
        myId = auth.user.id if auth.user is not None else None
        extra = f" or created_by = {user_id} " if myId == user_id else ""
        sql = dedent(
            f"""
            select
                version,
                count(id) as amount
            from note
            where
                keywords like '% {keywordsSql} %' and
                (is_shared = 'T' {extra})
            group by version
            ;
            """
        )
        records = NOTE_DB.executesql(sql)
        vrs = set()
        versionInfo = {}
        for (vr, amount) in records:
            vrs.add(vr)
            versionInfo[vr] = dict(n=amount)
        return versionInfo

    def inVerse(
        self, vr, bk, ch, vs, myId, clauseAtoms, changed, msgs, authenticated, edit
    ):
        NOTE_DB = current.NOTE_DB

        condition = "note.is_shared = 'T' or note.is_published = 'T' "
        if myId is not None:
            condition += f" or note.created_by = {myId} "

        noteSql = dedent(
            f"""
            select
                note.id,
                note.created_by as user_id,
                shebanq_web.auth_user.first_name,
                shebanq_web.auth_user.last_name,
                note.clause_atom,
                note.is_shared,
                note.is_published,
                note.published_on,
                note.status,
                note.keywords,
                note.ntext
            from note inner join shebanq_web.auth_user
            on note.created_by = shebanq_web.auth_user.id
            where
                ({condition})
            and
                note.version = '{vr}'
            and
                note.book ='{bk}'
            and
                note.chapter = {ch}
            and
                note.verse = {vs}
            order by
                modified_on desc
            ;
            """
        )

        records = NOTE_DB.executesql(noteSql)
        users = {}
        keyIndex = {}
        notesProto = collections.defaultdict(lambda: {})
        clauseAtomUsers = collections.defaultdict(lambda: collections.OrderedDict())
        if myId is not None and edit:
            users[myId] = "me"
            for clauseAtom in clauseAtoms:
                notesProto[clauseAtom][myId] = [
                    dict(
                        user_id=myId,
                        note_id=0,
                        is_shared=True,
                        is_published=False,
                        status="o",
                        keywords="",
                        ntext="",
                    )
                ]
                clauseAtomUsers[clauseAtom][myId] = None
        good = True
        if records is None:
            msgs.append(
                (
                    "error",
                    f"Cannot lookup notes for {bk} {ch}:{vs} in version {vr}",
                )
            )
            good = False
        elif len(records) == 0:
            msgs.append(("warning", "No notes"))
        else:
            now = current.request.utcnow

            for (
                note_id,
                user_id,
                first_name,
                last_name,
                clause_atom,
                is_shared,
                is_published,
                published_on,
                status,
                keywords,
                ntext,
            ) in records:
                if (
                    myId is None or (user_id != myId) or not edit
                ) and user_id not in users:
                    users[user_id] = f"{first_name} {last_name}"
                if user_id not in clauseAtomUsers[clause_atom]:
                    clauseAtomUsers[clause_atom][user_id] = None
                is_published = is_published == "T"
                is_shared = is_published or is_shared == "T"
                ro = (
                    myId is None
                    or user_id != myId
                    or not edit
                    or (
                        is_published
                        and published_on is not None
                        and (published_on <= now - PUBLISH_FREEZE)
                    )
                )
                keywordList = keywords.strip().split()
                for keyword in keywordList:
                    keyIndex[f"{user_id} {keyword}"] = iEncode(
                        "n", user_id, keywords=keyword
                    )
                notesProto.setdefault(clause_atom, {}).setdefault(user_id, []).append(
                    dict(
                        user_id=user_id,
                        note_id=note_id,
                        ro=ro,
                        is_shared=is_shared,
                        is_published=is_published,
                        status=status,
                        keywords=keywords,
                        ntext=ntext,
                    )
                )
        notes = {}
        for clauseAtom in notesProto:
            for user_id in clauseAtomUsers[clauseAtom]:
                notes.setdefault(clauseAtom, []).extend(notesProto[clauseAtom][user_id])

        return json.dumps(
            dict(
                good=good,
                changed=changed,
                msgs=msgs,
                users=users,
                notes=notes,
                keyIndex=keyIndex,
                authenticated=authenticated,
            )
        )

__init__(self, Books) special

Source code in modules/note.py
def __init__(self, Books):
    self.Books = Books

authUpload(self)

Source code in modules/note.py
def authUpload(self):
    auth = current.auth
    db = current.db

    myId = None
    authorized = False
    if auth.user:
        myId = auth.user.id
    if myId:
        sql = dedent(
            f"""
            select uid from uploaders where uid = {myId}
            """
        )
        records = db.executesql(sql)
        authorized = (
            records is not None and len(records) == 1 and records[0][0] == myId
        )
    msg = "" if authorized else "you are not allowed to upload notes as csv files"
    return (authorized, myId, msg)

page(self, ViewSettings)

Source code in modules/note.py
def page(self, ViewSettings):
    Check = current.Check

    pageConfig = ViewSettings.writeConfig()

    key_id = Check.isId("goto", "n", "note", [])

    (authorized, myId, msg) = self.authUpload()
    return dict(
        pageConfig=pageConfig,
        key_id=key_id,
        mayUpload=authorized,
        user_id=myId,
    )

getVerseNotes(self)

Get the notes belonging to a single verse.

Reads request parameters to determine which verse.

Source code in modules/note.py
def getVerseNotes(self):
    """Get the notes belonging to a single verse.

    Reads request parameters to determine which verse.
    """
    Check = current.Check
    auth = current.auth

    vr = Check.field("material", "", "version")
    bk = Check.field("material", "", "book")
    ch = Check.field("material", "", "chapter")
    vs = Check.field("material", "", "verse")
    edit = Check.isBool("edit")

    myId = None
    if auth.user:
        myId = auth.user.id
    authenticated = myId is not None

    clauseAtoms = self.getClauseAtoms(vr, bk, ch, vs)
    changed = False

    msgs = []

    return self.inVerse(
        vr, bk, ch, vs, myId, clauseAtoms, changed, msgs, authenticated, edit
    )

body(self)

Retrieves a note set record based on parameters.

Source code in modules/note.py
def body(self):
    """Retrieves a note set record based on parameters.
    """
    Check = current.Check

    vr = Check.field("material", "", "version")
    iidRep = Check.field("material", "", "iid")

    (iid, keywords) = iDecode("n", iidRep)
    msgs = []
    if not iid:
        msg = f"Not a valid note id: {iid}"
        msgs.append(("error", msg))
        return dict(
            noteRecord=dict(),
            note=json.dumps(dict()),
            msgs=json.dumps(msgs),
        )
    noteRecord = self.getInfo(iidRep, vr, msgs)
    return dict(
        vr=vr,
        noteRecord=noteRecord,
        note=json.dumps(noteRecord),
        msgs=json.dumps(msgs),
    )

getItems(self, vr, book, chapter, is_published)

Source code in modules/note.py
def getItems(self, vr, book, chapter, is_published):
    NOTE_DB = current.NOTE_DB

    bk = book["name"]
    ch = chapter["chapter_num"]
    if is_published == "x":
        isPubVersion = ""
    else:
        isPubVersion = " and is_published = 'T'"
    sql = dedent(
        f"""
        select
            note.created_by,
            shebanq_web.auth_user.first_name,
            shebanq_web.auth_user.last_name,
            note.keywords,
            note.verse,
            note.is_published
        from note
        inner join shebanq_web.auth_user
        on shebanq_web.auth_user.id = created_by
        where
            version = '{vr}' and
            book = '{bk}' and chapter = {ch} {isPubVersion}
        order by note.verse
        ;
        """
    )
    records = NOTE_DB.executesql(sql)
    user = {}
    nPublished = collections.Counter()
    nNotes = collections.Counter()
    nVerses = {}
    for (user_id, first_name, last_name, keywords, v, is_published) in records:
        if user_id not in user:
            user[user_id] = (first_name, last_name)
        for keyword in set(keywords.strip().split()):
            if is_published == "T":
                nPublished[(user_id, keyword)] += 1
            nNotes[(user_id, keyword)] += 1
            nVerses.setdefault((user_id, keyword), set()).add(v)
    r = []
    for (user_id, keyword) in nNotes:
        (first_name, last_name) = user[user_id]
        thisNPub = nPublished[(user_id, keyword)]
        thisNNotes = nNotes[(user_id, keyword)]
        thisNVerses = len(nVerses[(user_id, keyword)])
        r.append(
            {
                "item": dict(
                    id=iEncode("n", user_id, keyword),
                    first_name=first_name,
                    last_name=last_name,
                    keywords=keyword,
                    is_published=thisNPub > 0,
                    nNotes=thisNNotes,
                    nVerses=thisNVerses,
                ),
                "slots": json.dumps([]),
            }
        )
    return r

read(self, vr, iid, keywords)

Source code in modules/note.py
def read(self, vr, iid, keywords):
    auth = current.auth
    NOTE_DB = current.NOTE_DB

    clauseAtomFirst = self.getClauseAtomFirstSlot(vr)
    keywordsSql = keywords.replace("'", "''")
    myId = auth.user.id if auth.user is not None else None
    extra = "" if myId is None else f" or created_by = {myId} "
    sql = dedent(
        f"""
        select book, clause_atom from note
        where keywords like '% {keywordsSql} %'
        and version = '{vr}' and (is_shared = 'T' {extra})
        ;
        """
    )
    clauseAtoms = NOTE_DB.executesql(sql)
    slots = {clauseAtomFirst[x[0]][x[1]] for x in clauseAtoms}
    return normRanges(None, fromset=slots)

getClauseAtoms(self, vr, bk, ch, vs)

Source code in modules/note.py
def getClauseAtoms(self, vr, bk, ch, vs):
    Caching = current.Caching
    return Caching.get(
        f"clause_atoms_{vr}_{bk}_{ch}_{vs}_",
        lambda: self.getClauseAtoms_c(vr, bk, ch, vs),
        ALWAYS,
    )

getClauseAtoms_c(self, vr, bk, ch, vs)

Source code in modules/note.py
def getClauseAtoms_c(self, vr, bk, ch, vs):
    PASSAGE_DBS = current.PASSAGE_DBS

    clauseAtoms = []
    caData = (
        PASSAGE_DBS[vr].executesql(
            dedent(
                f"""
                select
                   distinct word.clause_atom_number
                from
                    verse
                inner join
                    word_verse on verse.id = word_verse.verse_id
                inner join
                    word on word.word_number = word_verse.anchor
                inner join
                    chapter on chapter.id = verse.chapter_id
                inner join
                    book on book.id = chapter.book_id
                where
                    book.name = '{bk}' and
                    chapter.chapter_num = {ch} and
                    verse.verse_num = {vs}
                order by
                    word.clause_atom_number
                ;
                """
            )
        )
        if vr in PASSAGE_DBS
        else []
    )

    for row in caData:
        clauseAtoms.append(row[0])
    return clauseAtoms

getClauseAtomFirstSlot(self, vr)

Source code in modules/note.py
def getClauseAtomFirstSlot(self, vr):
    Caching = current.Caching

    return Caching.get(
        f"clause_atom_f_{vr}_",
        lambda: self.getClauseAtomFirstSlot_c(vr),
        ALWAYS,
    )

getClauseAtomFirstSlot_c(self, vr)

Source code in modules/note.py
def getClauseAtomFirstSlot_c(self, vr):
    Books = self.Books
    PASSAGE_DBS = current.PASSAGE_DBS

    (books, booksOrder, bookIds, bookName) = Books.get(vr)
    sql = dedent(
        """
        select book_id, ca_num, first_m
        from clause_atom
        ;
        """
    )
    caData = PASSAGE_DBS[vr].executesql(sql) if vr in PASSAGE_DBS else []
    caFirst = {}
    for (book_id, ca_num, first_m) in caData:
        book_name = bookName[book_id]
        caFirst.setdefault(book_name, {})[ca_num] = first_m
    return caFirst

getInfo(self, iidRep, vr, msgs)

Source code in modules/note.py
def getInfo(self, iidRep, vr, msgs):
    db = current.db

    (iid, keywords) = iDecode("n", iidRep)
    if iid is None:
        return {}
    nRecord = dict(
        id=iidRep,
        user_id=iid,
        first_name="N?",
        last_name="N?",
        keywords=keywords,
        versions={},
    )
    nRecord["versions"] = self.countNotes(iid, keywords)
    sql = dedent(
        f"""
        select first_name, last_name from auth_user where id = '{iid}'
        ;
        """
    )
    uinfo = db.executesql(sql)
    if uinfo is not None and len(uinfo) > 0:
        nRecord["first_name"] = uinfo[0][0]
        nRecord["last_name"] = uinfo[0][1]
    return nRecord

countNotes(self, user_id, keywords)

Source code in modules/note.py
def countNotes(self, user_id, keywords):
    auth = current.auth
    NOTE_DB = current.NOTE_DB

    keywordsSql = keywords.replace("'", "''")
    myId = auth.user.id if auth.user is not None else None
    extra = f" or created_by = {user_id} " if myId == user_id else ""
    sql = dedent(
        f"""
        select
            version,
            count(id) as amount
        from note
        where
            keywords like '% {keywordsSql} %' and
            (is_shared = 'T' {extra})
        group by version
        ;
        """
    )
    records = NOTE_DB.executesql(sql)
    vrs = set()
    versionInfo = {}
    for (vr, amount) in records:
        vrs.add(vr)
        versionInfo[vr] = dict(n=amount)
    return versionInfo

inVerse(self, vr, bk, ch, vs, myId, clauseAtoms, changed, msgs, authenticated, edit)

Source code in modules/note.py
def inVerse(
    self, vr, bk, ch, vs, myId, clauseAtoms, changed, msgs, authenticated, edit
):
    NOTE_DB = current.NOTE_DB

    condition = "note.is_shared = 'T' or note.is_published = 'T' "
    if myId is not None:
        condition += f" or note.created_by = {myId} "

    noteSql = dedent(
        f"""
        select
            note.id,
            note.created_by as user_id,
            shebanq_web.auth_user.first_name,
            shebanq_web.auth_user.last_name,
            note.clause_atom,
            note.is_shared,
            note.is_published,
            note.published_on,
            note.status,
            note.keywords,
            note.ntext
        from note inner join shebanq_web.auth_user
        on note.created_by = shebanq_web.auth_user.id
        where
            ({condition})
        and
            note.version = '{vr}'
        and
            note.book ='{bk}'
        and
            note.chapter = {ch}
        and
            note.verse = {vs}
        order by
            modified_on desc
        ;
        """
    )

    records = NOTE_DB.executesql(noteSql)
    users = {}
    keyIndex = {}
    notesProto = collections.defaultdict(lambda: {})
    clauseAtomUsers = collections.defaultdict(lambda: collections.OrderedDict())
    if myId is not None and edit:
        users[myId] = "me"
        for clauseAtom in clauseAtoms:
            notesProto[clauseAtom][myId] = [
                dict(
                    user_id=myId,
                    note_id=0,
                    is_shared=True,
                    is_published=False,
                    status="o",
                    keywords="",
                    ntext="",
                )
            ]
            clauseAtomUsers[clauseAtom][myId] = None
    good = True
    if records is None:
        msgs.append(
            (
                "error",
                f"Cannot lookup notes for {bk} {ch}:{vs} in version {vr}",
            )
        )
        good = False
    elif len(records) == 0:
        msgs.append(("warning", "No notes"))
    else:
        now = current.request.utcnow

        for (
            note_id,
            user_id,
            first_name,
            last_name,
            clause_atom,
            is_shared,
            is_published,
            published_on,
            status,
            keywords,
            ntext,
        ) in records:
            if (
                myId is None or (user_id != myId) or not edit
            ) and user_id not in users:
                users[user_id] = f"{first_name} {last_name}"
            if user_id not in clauseAtomUsers[clause_atom]:
                clauseAtomUsers[clause_atom][user_id] = None
            is_published = is_published == "T"
            is_shared = is_published or is_shared == "T"
            ro = (
                myId is None
                or user_id != myId
                or not edit
                or (
                    is_published
                    and published_on is not None
                    and (published_on <= now - PUBLISH_FREEZE)
                )
            )
            keywordList = keywords.strip().split()
            for keyword in keywordList:
                keyIndex[f"{user_id} {keyword}"] = iEncode(
                    "n", user_id, keywords=keyword
                )
            notesProto.setdefault(clause_atom, {}).setdefault(user_id, []).append(
                dict(
                    user_id=user_id,
                    note_id=note_id,
                    ro=ro,
                    is_shared=is_shared,
                    is_published=is_published,
                    status=status,
                    keywords=keywords,
                    ntext=ntext,
                )
            )
    notes = {}
    for clauseAtom in notesProto:
        for user_id in clauseAtomUsers[clauseAtom]:
            notes.setdefault(clauseAtom, []).extend(notesProto[clauseAtom][user_id])

    return json.dumps(
        dict(
            good=good,
            changed=changed,
            msgs=msgs,
            users=users,
            notes=notes,
            keyIndex=keyIndex,
            authenticated=authenticated,
        )
    )