Coverage for rfpy/model/composite.py: 100%
44 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-31 16:00 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-31 16:00 +0000
1import logging
2from hashlib import md5
3from typing import List
5from sqlalchemy import types, Column, ForeignKey, Index
6from sqlalchemy.orm import backref, relationship
7from sqlalchemy.dialects.mysql import insert
9from .meta import Base
10from .questionnaire import QuestionDefinition
12log = logging.getLogger(__name__)
15class QuestionMeta(Base):
16 __tablename__ = "questions_meta"
17 __table_args__ = (
18 Index("ft_alltext", "alltext", mysql_prefix="FULLTEXT"),
19 {"mysql_engine": "InnoDB", "mysql_charset": "utf8mb4"},
20 )
22 question_id = Column(
23 types.Integer,
24 ForeignKey("questions.id", ondelete="CASCADE"),
25 unique=True,
26 nullable=False
27 )
28 question_def = relationship(
29 'QuestionDefinition',
30 backref=backref(
31 'meta',
32 uselist=False,
33 cascade='all,delete',
34 passive_deletes=True
35 )
36 )
37 question_instance = relationship(
38 'QuestionInstance',
39 secondary=QuestionDefinition.__table__,
40 sync_backref=False,
41 backref=backref('meta', uselist=False, viewonly=True),
42 viewonly=True
43 )
45 # For efficient & simplified full text querying of questions
46 alltext = Column(types.TEXT)
48 # Identify the structure of the input elements for uniform reporting
49 # similar to the yesno reports developed for T2R
50 # Support for 'questions like this' functionality
51 signature = Column(types.VARCHAR(64), index=True)
53 def __repr__(self) -> str:
54 return f'<QuestionMeta, QDef # {self.question_id}, signature: {self.signature}>'
57def qsignature(sig_list: List[str]) -> str:
58 return md5("|".join(sig_list).encode('utf8')).hexdigest()
61def update_meta_row_stmt(q):
62 qtext_set = [q.title]
63 sig_list = []
64 for el in q.elements:
65 sig_list.append(f"{el.el_type}-{el.row}-{el.col}")
66 if el.el_type in ('LB', 'CB') and el.label is not None:
67 qtext_set.append(el.label)
68 if el.choices:
69 for c in el.choices:
70 if 'label' in c and c['label'] is not None:
71 qtext_set.append(c['label'])
72 qtext = " ".join(qtext_set)
73 sig = qsignature(sig_list)
74 stmt = insert(QuestionMeta).values(
75 question_id=q.id,
76 alltext=qtext,
77 signature=sig
78 )
79 return stmt.on_duplicate_key_update(
80 alltext=qtext,
81 signature=sig
82 )
85def update_qmeta_table(session, qdef_ids):
86 '''
87 Update the questions_meta database table for the given QuestionDefintion ids. This function
88 should be called whenever a question defintion is inserted or updated.
90 Each row of the questions_meta table maps to one QuestionDefinition and provides a FullText
91 indexed column for easy searching and a regularly indexed "signature" column to allow
92 questions with the identical structures to be identified for uniform reporting
93 '''
94 conn = session.connection()
95 for q in (session.query(QuestionDefinition)
96 .filter(QuestionDefinition.id.in_(qdef_ids))):
97 try:
98 dupe_update = update_meta_row_stmt(q)
99 res = conn.execute(dupe_update)
100 m = 'Execute update question meta statement for Q ID # %s : updated %s rows'
101 log.info(m, q.id, res.rowcount)
102 except Exception as exc:
103 log.error('Failed to update meta table for question Id %s with error: %s', q.id, exc)