def next_edge(self, edge, fsmStack, request, useCurrent=False, **kwargs): fsm = edge.fromNode.fsm next_node = edge.toNode ul_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_ul_id') if fsmStack.next_point.text.lower() == 'yes': actual_faq_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_faq_id', None) faq = Response.objects.filter(id=int(actual_faq_id)).first() try: ob, _ = InquiryCount.objects.get_or_create( response=faq, addedBy=request.user) except InquiryCount.MultipleObjectsReturned: ob = InquiryCount.objects.filter( response=faq, addedBy=request.user).order_by('-atime').first() faq.notify_instructors() c_chat_context().update_one( {"chat_id": fsmStack.id}, {"$set": { "actual_inquiry_id": ob.id }}, ) faq_answers = faq.response_set.all() if faq_answers: next_node = fsm.get_node('SHOW_FAQ_ANSWERS') c_faq_data().update_one( { "chat_id": fsmStack.id, "ul_id": ul_id }, { "$set": { "faqs.{}.answers".format(actual_faq_id): [{ "done": False, "answer_id": answer.id } for answer in faq_answers] } }) else: next_node = fsm.get_node('WILL_TRY_MESSAGE_2') else: show_another_faq = False for key, value in list( self.get_pending_faqs(chat_id=fsmStack.id, ul_id=ul_id).items()): if not value.get('status').get('done', False): show_another_faq = True break if show_another_faq: next_node = fsm.get_node('SHOW_FAQ_BY_ONE') return next_node
def start_event(self, node, fsmStack, request, **kwargs): """ Event handler for START node. """ unit = fsmStack.state.get_data_attr('unit') fsmStack.state.title = 'Study: %s' % unit.title try: # use unitStatus if provided unitStatus = fsmStack.state.get_data_attr('unitStatus') except AttributeError: # create new, empty unitStatus unitStatus = UnitStatus(unit=unit, user=request.user) unitStatus.save() fsmStack.state.set_data_attr('unitStatus', unitStatus) unit_lesson, chat = kwargs.get('unitlesson'), kwargs.get('chat') fsmStack.state.unitLesson = unit_lesson fsmStack.state.set_data_attr('updates', kwargs.get('updates', False)) fsmStack.state.set_data_attr('new_faqs', kwargs.get( 'new_faqs', False)) if kwargs.get('new_faqs') else None faqs_for_ul = unit_lesson.response_set.filter( ~Q(author=request.user), kind=Response.STUDENT_QUESTION, is_preview=False, is_test=False).exclude(title__isnull=True).exclude( title__exact='').exists() if faqs_for_ul: c_faq_data().update_one( { "chat_id": chat.id, "ul_id": unit_lesson.id }, {"$set": { "faqs": {} }}, upsert=True) _next = 'show_faq' if faqs_for_ul else 'ask_new_faq' return fsmStack.state.transition(fsmStack, request, _next, useCurrent=True, **kwargs)
def get_faqs(self): if self.text: return self.text # FIXME UPDATE -> FAQ -> get_faqs ['updates', 'new_faqs'], this transition potentially cause bugs state = self.chat.state updates = state.get_data_attr( 'updates' ) if state and 'updates' in state.load_json_data() else None new_faqs = state.get_data_attr( 'new_faqs' ) if state and 'new_faqs' in state.load_json_data() else None faqs = None faq_list = self.content.response_set.filter( ~Q(author=self.owner), kind=Response.STUDENT_QUESTION, is_preview=False, is_test=False ).exclude(title__isnull=True).exclude(title__exact='')\ .annotate(num_inquiry=Count('inquirycount')).order_by('-num_inquiry') if updates and new_faqs: faq_list = faq_list.filter( id__in=[faq.get('faq_id') for faq in new_faqs]) if faq_list: checked_faqs = c_faq_data().find_one( { 'chat_id': self.chat.id, "ul_id": self.content_id }, { 'faqs': 1, '_id': 0 }) faq_str = ( '<li><div class="chat-check chat-selectable {}" data-selectable-attribute="faqModel" ' 'data-selectable-value="{:d}"></div><h3>{}</h3></li>') faqs = reduce(lambda x, y: x + y, [ faq_str.format( 'chat-selectable-selected' if str(x.id) in list( checked_faqs.get('faqs', {}).keys()) else '', x.id, x.title if x.title else Truncator(x.text).words( 72, truncate=' ...')) for x in faq_list ]) return '<ul class="chat-select-list">{}</ul>'.format( faqs or '<li><h3>There are no faqs to display.</h3></li>')
def next_edge(self, edge, fsmStack, request, useCurrent=False, **kwargs): fsm = edge.fromNode.fsm ul_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_ul_id') actual_faq_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_faq_id', None) if actual_faq_id: faq_answers = c_faq_data().find_one({ "chat_id": fsmStack.id, "ul_id": ul_id, "faqs.{}.answers.done".format(actual_faq_id): False }) next_node = fsm.get_node( 'SHOW_FAQ_ANSWERS') if faq_answers else fsm.get_node( 'ASK_UNDERSTANDING') return next_node return edge.toNode
def get_pending_faqs(chat_id, ul_id): # TODO change to the Assignment expressions in Python3.8 faq_data = c_faq_data().find_one({"chat_id": chat_id, "ul_id": ul_id}) return faq_data.get('faqs', {}) if faq_data else {}
def get_message(self, chat, request, current=None, message=None): stack_pattern = QUESTION_STACK_PATTERN.format(request.user.id, chat.id) faq_response_pattern = QUESTION_STACK_PATTERN.format(request.user.id, chat.id) is_additional = chat.state.fsmNode.fsm.fsm_name_is_one_of('additional', 'resource') next_lesson = chat.state.unitLesson if hasattr(self._plugin, 'get_message'): return self._plugin.get_message(chat, next_lesson, is_additional, node=self) if self.node_name_is_one_of('LESSON'): input_type = 'custom' kind = next_lesson.lesson.kind try: if is_additional: raise UnitLesson.DoesNotExist unitStatus = chat.state.get_data_attr('unitStatus') next_ul = unitStatus.unit.unitlesson_set.get(order=unitStatus.order+1) # Add CONTINUE button here if (next_ul and next_ul.lesson.kind in SIMILAR_KINDS and kind in SIMILAR_KINDS or next_ul.lesson.kind == Lesson.ORCT_QUESTION): input_type = 'options' kind = 'button' except UnitLesson.DoesNotExist: pass message = Message.objects.get_or_create( contenttype='unitlesson', content_id=next_lesson.id, chat=chat, owner=chat.user, input_type=input_type, kind=kind, is_additional=is_additional)[0] if self.name == 'ASK': SUB_KIND_TO_KIND_MAP = { 'choices': 'button', } SUBKIND_TO_INPUT_TYPE_MAP = { 'choices': 'options', } sub_kind = next_lesson.lesson.sub_kind _data = { 'contenttype': 'unitlesson', 'content_id': next_lesson.id, 'chat': chat, 'owner': chat.user, 'input_type': 'custom', # SUBKIND_TO_INPUT_TYPE_MAP.get(sub_kind, 'custom'), 'kind': next_lesson.lesson.kind, # SUB_KIND_TO_KIND_MAP.get(sub_kind, next_lesson.lesson.kind), 'is_additional': is_additional } if not self.fsm.fsm_name_is_one_of('live_chat'): message, created = Message.objects.get_or_create(**_data) else: message = Message(**_data) message.save() find_crit = { "stack_id": stack_pattern } c_chat_stack().update_one( find_crit, {"$push": {"stack": next_lesson.id}}, upsert=True) # Fallback method to pass ul_id's throught messages if request.session.get(stack_pattern): if isinstance(request.session[stack_pattern], list): request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [request.session[stack_pattern]] request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [next_lesson.id] if self.name == 'ADDITIONAL_ASK': SUB_KIND_TO_KIND_MAP = { 'choices': 'button', } SUBKIND_TO_INPUT_TYPE_MAP = { 'choices': 'options', } sub_kind = next_lesson.lesson.sub_kind _data = { 'contenttype': 'unitlesson', 'content_id': next_lesson.id, 'chat': chat, 'owner': chat.user, 'input_type': 'custom', # SUBKIND_TO_INPUT_TYPE_MAP.get(sub_kind, 'custom'), 'kind': 'message', # SUB_KIND_TO_KIND_MAP.get(sub_kind, next_lesson.lesson.kind), 'is_additional': is_additional } if not self.fsm.fsm_name_is_one_of('live_chat'): message, created = Message.objects.get_or_create(**_data) else: message = Message(**_data) message.save() find_crit = { "stack_id": stack_pattern } c_chat_stack().update_one( find_crit, {"$push": {"stack": next_lesson.id}}, upsert=True) # Fallback method to pass ul_id's throught messages if request.session.get(stack_pattern): if isinstance(request.session[stack_pattern], list): request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [request.session[stack_pattern]] request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [next_lesson.id] if self.node_name_is_one_of('ABORTS'): message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=self.title ) if self.node_name_is_one_of('GET_ABORTS'): message = Message.objects.get_or_create( contenttype='NoneType', kind='abort', input_type='options', chat=chat, owner=chat.user, userMessage=False, is_additional=is_additional)[0] if self.node_name_is_one_of('GET_ANSWER'): find_crit = { "stack_id": stack_pattern } stack = None try: document = c_chat_stack().find_and_modify( query=find_crit, update={"$pop": {"stack": 1}}) stack = document.get('stack', []) except ConnectionFailure: pass if request.session.get(stack_pattern): if isinstance(request.session[stack_pattern], list): fallback_ul_id = request.session[stack_pattern].pop() elif isinstance(request.session[stack_pattern], int): fallback_ul_id = request.session[stack_pattern] else: fallback_ul_id = current.id unit_lesson_id = stack.pop() if stack else fallback_ul_id lesson_to_answer = UnitLesson.objects.filter(id=unit_lesson_id).first() _data = { 'contenttype': 'response', 'input_type': 'text', 'lesson_to_answer': lesson_to_answer, 'chat': chat, 'owner': chat.user, 'kind': 'response', 'userMessage': True, 'is_additional': is_additional } if lesson_to_answer.lesson.sub_kind == 'choices': _data.update(dict( input_type='options', )) if not self.fsm.fsm_name_is_one_of('live_chat'): message = Message.objects.get_or_create(**_data)[0] else: message = Message(**_data) message.save() if self.node_name_is_one_of('ADDITIONAL_GET_ANSWER'): find_crit = { "stack_id": stack_pattern } stack = None try: document = c_chat_stack().find_and_modify( query=find_crit, update={"$pop": {"stack": 1}}) stack = document.get('stack', []) except ConnectionFailure: pass if request.session.get(stack_pattern): if isinstance(request.session[stack_pattern], list): fallback_ul_id = request.session[stack_pattern].pop() elif isinstance(request.session[stack_pattern], int): fallback_ul_id = request.session[stack_pattern] else: fallback_ul_id = current.id unit_lesson_id = stack.pop() if stack else fallback_ul_id lesson_to_answer = UnitLesson.objects.filter(id=unit_lesson_id).first() _data = { 'contenttype': 'response', 'input_type': 'text', 'lesson_to_answer': lesson_to_answer, 'chat': chat, 'owner': chat.user, 'kind': 'response', 'userMessage': True, 'is_additional': is_additional } if lesson_to_answer.lesson.sub_kind == 'choices': _data.update(dict( input_type='options', )) if not self.fsm.fsm_name_is_one_of('live_chat'): message = Message.objects.get_or_create(**_data)[0] else: message = Message(**_data) message.save() if self.node_name_is_one_of('CONFIDENCE'): # current here is Response instance if isinstance(current, Response): response_to_chk = current answer = current.unitLesson.get_answers().first() else: response_to_chk = message.response_to_check if not message.lesson_to_answer: answer = message.response_to_check.unitLesson.get_answers().first() else: answer = message.lesson_to_answer.get_answers().first() message = Message.objects.create( # get_or_create contenttype='unitlesson', response_to_check=response_to_chk, input_type='custom', text=self.title, chat=chat, owner=chat.user, kind=answer.kind, is_additional=is_additional) if self.node_name_is_one_of('ADDITIONAL_CONFIDENCE'): # current here is Response instance if isinstance(current, Response): response_to_chk = current answer = current.unitLesson.get_answers().first() else: response_to_chk = message.response_to_check if not message.lesson_to_answer: answer = message.response_to_check.unitLesson.get_answers().first() else: answer = message.lesson_to_answer.get_answers().first() message = Message.objects.create( # get_or_create contenttype='unitlesson', response_to_check=response_to_chk, input_type='custom', text=self.title, chat=chat, owner=chat.user, kind=answer.kind, is_additional=is_additional) if self.node_name_is_one_of('GET_CONFIDENCE'): _data = dict( contenttype='response', content_id=message.response_to_check.id, input_type='options', chat=chat, owner=chat.user, kind='response', userMessage=True, is_additional=is_additional, ) # here was Message.objects.create for all fsm's except live_chat. for live_chat fsm here was get_or_create message = Message(**_data) message.save() if self.node_name_is_one_of('ADDITIONAL_GET_CONFIDENCE'): _data = dict( contenttype='response', content_id=message.response_to_check.id, input_type='options', chat=chat, owner=chat.user, kind='response', userMessage=True, is_additional=is_additional, ) # here was Message.objects.create for all fsm's except live_chat. for live_chat fsm here was get_or_create message = Message(**_data) message.save() if self.node_name_is_one_of('CORRECT_ANSWER'): lesson = message.response_to_check.unitLesson.lesson if chat.is_live else message.content.unitLesson.lesson correct_choices = lesson.get_correct_choices() if correct_choices: correct_title = lesson.get_choice_title(correct_choices[0][0]) correct_description = lesson.get_choice_description(correct_choices[0][0]) else: answer = message.content.unitLesson.get_answers().first() correct_title = answer.lesson.title if answer else 'Answer title' correct_description = mark_safe(md2html(answer.lesson.text)) if answer else 'Answer description' message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>You got it right, the correct answer is: {}</b> <br> {} """ .format(correct_title, correct_description) ) if self.node_name_is_one_of('ADDITIONAL_CORRECT_ANSWER'): lesson = message.response_to_check.unitLesson.lesson if chat.is_live else message.content.unitLesson.lesson correct_choices = lesson.get_correct_choices() if correct_choices: correct_title = lesson.get_choice_title(correct_choices[0][0]) correct_description = lesson.get_choice_description(correct_choices[0][0]) else: answer = message.content.unitLesson.get_answers().first() correct_title = answer.lesson.title if answer else 'Answer title' correct_description = mark_safe(md2html(answer.lesson.text)) if answer else 'Answer description' message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>You got it right, the correct answer is: {}</b> <br> {} """ .format(correct_title, correct_description) ) if self.node_name_is_one_of('INCORRECT_ANSWER'): lesson = message.response_to_check.unitLesson.lesson if chat.is_live else message.content.unitLesson.lesson correct_choices = lesson.get_correct_choices() if correct_choices: correct_title = lesson.get_choice_title(correct_choices[0][0]) correct_description = lesson.get_choice_description(correct_choices[0][0]) else: answer = message.content.unitLesson.get_answers().first() correct_title = answer.lesson.title if answer else 'Answer title' correct_description = mark_safe(md2html(answer.lesson.text)) if answer else 'Answer description' message = Message.objects.create( owner=chat.user, chat=chat, lesson_to_answer=message.response_to_check.unitLesson if chat.is_live else message.content.unitLesson, response_to_check=message.response_to_check if chat.is_live else current, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>The correct answer is: {}</b> <br> {} """ .format(correct_title, correct_description) ) if self.node_name_is_one_of('ADDITIONAL_INCORRECT_ANSWER'): lesson = message.response_to_check.unitLesson.lesson if chat.is_live else message.content.unitLesson.lesson correct_choices = lesson.get_correct_choices() if correct_choices: correct_title = lesson.get_choice_title(correct_choices[0][0]) correct_description = lesson.get_choice_description(correct_choices[0][0]) else: answer = message.content.unitLesson.get_answers().first() correct_title = answer.lesson.title if answer else 'Answer title' correct_description = mark_safe(md2html(answer.lesson.text)) if answer else 'Answer description' message = Message.objects.create( owner=chat.user, chat=chat, lesson_to_answer=message.response_to_check.unitLesson if chat.is_live else message.content.unitLesson, response_to_check=message.response_to_check if chat.is_live else current, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>The correct answer is: {}</b> <br> {} """ .format(correct_title, correct_description) ) if self.node_name_is_one_of('INCORRECT_CHOICE'): lesson = message.lesson_to_answer.lesson selected = [int(i) for i in message.response_to_check.text.split('[selected_choices] ')[1].split()] incorrect_description = lesson.get_choice_description(selected[0]) if selected else '' my_choices = [] for i, c in message.response_to_check.lesson.get_choices(): if i in selected: my_choices.append(c.split(' ', 1)[1]) if not my_choices: my_choices.append('Nothing') message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>You selected: {}</b> <br> {} """ .format( my_choices[0] if len(my_choices) == 1 else '<br>' + ''.join( ['<h3>{}</h3>'.format(_) for _ in my_choices]), incorrect_description ) ) if self.node_name_is_one_of('ADDITIONAL_INCORRECT_CHOICE'): lesson = message.lesson_to_answer.lesson selected = [int(i) for i in message.response_to_check.text.split('[selected_choices] ')[1].split()] incorrect_description = lesson.get_choice_description(selected[0]) if selected else '' my_choices = [] for i, c in message.response_to_check.lesson.get_choices(): if i in selected: my_choices.append(c.split(' ', 1)[1]) if not my_choices: my_choices.append('Nothing') message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=""" <b>You selected: {}</b> <br> {} """ .format( my_choices[0] if len(my_choices) == 1 else '<br>' + ''.join( ['<h3>{}</h3>'.format(_) for _ in my_choices]), incorrect_description ) ) # here was Message.objects.create for all fsm's except live_chat. for live_chat fsm here was get_or_create if self.node_name_is_one_of("WAIT_ASSESS"): if isinstance(current, Response): resp_to_chk = current else: resp_to_chk = message.response_to_check message = Message.objects.get_or_create( chat=chat, text=self.title, kind='button', response_to_check=resp_to_chk, is_additional=is_additional, owner=chat.user, )[0] if self.node_name_is_one_of('ASSESS'): # current here is Response instance if isinstance(current, Response): response_to_chk = current answer = current.unitLesson.get_answers().first() else: response_to_chk = message.response_to_check if not message.lesson_to_answer: answer = message.response_to_check.unitLesson.get_answers().first() else: answer = message.lesson_to_answer.get_answers().first() message = Message.objects.get_or_create( contenttype='unitlesson', response_to_check=response_to_chk, input_type='custom', content_id=answer.id, chat=chat, owner=chat.user, kind=answer.kind, is_additional=is_additional)[0] if self.node_name_is_one_of('ADDITIONAL_ASSESS'): # current here is Response instance if isinstance(current, Response): response_to_chk = current answer = current.unitLesson.get_answers().first() else: response_to_chk = message.response_to_check if not message.lesson_to_answer: answer = message.response_to_check.unitLesson.get_answers().first() else: answer = message.lesson_to_answer.get_answers().first() message = Message.objects.get_or_create( contenttype='unitlesson', response_to_check=response_to_chk, input_type='custom', content_id=answer.id, chat=chat, owner=chat.user, kind=answer.kind, is_additional=is_additional)[0] if self.node_name_is_one_of('GET_ASSESS'): _data = dict( contenttype='response', content_id=message.response_to_check.id if message.response_to_check else None, input_type='options', chat=chat, owner=chat.user, kind='response', userMessage=True, is_additional=is_additional ) # here was Message.objects.create for all fsm's except live_chat. for live_chat fsm here was get_or_create message = Message(**_data) message.save() if self.node_name_is_one_of('ADDITIONAL_GET_ASSESS'): _data = dict( contenttype='response', content_id=message.response_to_check.id if message.response_to_check else None, input_type='options', chat=chat, owner=chat.user, kind='response', userMessage=True, is_additional=is_additional ) # here was Message.objects.create for all fsm's except live_chat. for live_chat fsm here was get_or_create message = Message(**_data) message.save() if self.node_name_is_one_of('GRADING'): GraderClass = GRADERS.get(message.content.unitLesson.lesson.sub_kind) if GraderClass: grader = GraderClass(message.content.unitLesson, message.content) # grade method must be called to actually do the work grader.grade text = 'Your answer is {}!'.format(grader.message) else: text = "No such grader! Grading could not be applied." message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=text ) if self.node_name_is_one_of('ADDITIONAL_GRADING'): GraderClass = GRADERS.get(message.content.unitLesson.lesson.sub_kind) if GraderClass: grader = GraderClass(message.content.unitLesson, message.content) # grade method must be called to actually do the work grader.grade text = 'Your answer is {}!'.format(grader.message) else: text = "No such grader! Grading could not be applied." message = Message.objects.create( owner=chat.user, chat=chat, kind='message', input_type='custom', is_additional=is_additional, text=text ) if self.node_name_is_one_of('STUDENTERROR'): resolve_message = Message.objects.filter( contenttype='unitlesson', content_id=next_lesson.id, chat=chat, owner=chat.user, input_type='custom', kind='message', timestamp__isnull=True, is_additional=True).first() message = Message.objects.get_or_create( contenttype='unitlesson', content_id=resolve_message.student_error.errorModel.id, chat=chat, owner=chat.user, student_error=resolve_message.student_error, input_type='options', kind='button', is_additional=True)[0] c_chat_stack().update_one( {"stack_id": stack_pattern}, {"$set": {"additional_stack": { "em_id": resolve_message.student_error.errorModel.id, "student_error_id": resolve_message.student_error.id }}}, upsert=True) if self.node_name_is_one_of('RESOLVE'): SUB_KIND_TO_KIND_MAP = { 'choices': 'button', } SUBKIND_TO_INPUT_TYPE_MAP = { 'choices': 'options', } sub_kind = next_lesson.lesson.sub_kind _data = { 'contenttype': 'unitlesson', 'content_id': next_lesson.id, 'chat': chat, 'owner': chat.user, 'input_type': 'custom', # SUBKIND_TO_INPUT_TYPE_MAP.get(sub_kind, 'custom'), 'kind': 'message', # SUB_KIND_TO_KIND_MAP.get(sub_kind, next_lesson.lesson.kind), 'is_additional': is_additional } if not self.fsm.fsm_name_is_one_of('live_chat'): filter_data = _data.copy() filter_data.update({'timestamp__isnull': True}) message = Message.objects.filter(**filter_data).first() if not message: message = Message.objects.create(**_data) else: message = Message(**_data) message.save() if next_lesson.lesson.kind == 'orct': c_chat_stack().update_one( {"stack_id": stack_pattern}, {"$push": {"stack": next_lesson.id}}, upsert=True) # Fallback method to pass ul_id's throught messages if request.session.get(stack_pattern): if isinstance(request.session[stack_pattern], list): request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [request.session[stack_pattern]] request.session[stack_pattern].append(next_lesson.id) else: request.session[stack_pattern] = [next_lesson.id] if self.node_name_is_one_of('HELP_RESOLVE'): message = Message.objects.get_or_create( contenttype='unitlesson', content_id=next_lesson.id, chat=chat, owner=chat.user, input_type='options', kind='button', timestamp__isnull=True, is_additional=True)[0] if self.node_name_is_one_of('MESSAGE_NODE'): additional_info = c_chat_stack().find_one( {"stack_id": stack_pattern}, {"additional_stack": 1, "_id": 0}).get("additional_stack") student_error_id, _ = additional_info.get('student_error_id'), additional_info.get('em_id') # FIXME message = Message.objects.get_or_create( chat=chat, owner=chat.user, text=chat.state.fsmNode.title, student_error=StudentError.objects.filter(id=student_error_id).first(), input_type='custom', kind='message', is_additional=True)[0] if self.node_name_is_one_of( 'END', 'IF_RESOURCES', 'NEED_HELP_MESSAGE', 'ASSESS_QUESTION_MESSAGE', 'ADDITIONAL_ASSESS_QUESTION_MESSAGE' ): if not self.help: text = self.get_help(chat.state, request=None) else: text = self.help message = Message.objects.create( response_to_check=message.response_to_check, chat=chat, owner=chat.user, text=text, input_type='custom', kind='message', is_additional=True) if self.node_name_is_one_of('GREAT_MESSAGE', 'HOPENESS_MESSAGE', 'ORCT_LETS_START_MESSAGE'): if not self.help: text = self.get_help(chat.state, request=None) else: text = self.help message = Message.objects.create( response_to_check=message.response_to_check, chat=chat, owner=chat.user, text=text, input_type='custom', kind='message', is_additional=True) if self.node_name_is_one_of('GET_RESOLVE'): message = Message.objects.create( contenttype='unitlesson', content_id=next_lesson.id, input_type='options', chat=chat, owner=chat.user, student_error=message.student_error, kind='response', userMessage=True, is_additional=is_additional) if self.node_name_is_one_of('ERRORS'): message = Message.objects.get_or_create( chat=chat, owner=chat.user, text='''''' '''Here are the most common blindspots people reported when comparing their answer vs. ''' '''the correct answer. Check the box(es) that seem relevant to your answer (if any).''', kind='message', input_type='custom', is_additional=is_additional)[0] if self.node_name_is_one_of('GET_ERRORS'): uniterror = UnitError.get_by_message(message) message = Message.objects.get_or_create( contenttype='uniterror', content_id=uniterror.id, input_type='options', chat=chat, kind='uniterror', owner=chat.user, userMessage=False, is_additional=is_additional)[0] if self.node_name_is_one_of('SHOW_FAQS'): is_new = chat.state.get_data_attr('updates') if 'updates' in chat.state.load_json_data() else None message = Message.objects.create( contenttype='unitlesson', content_id=next_lesson.id, input_type='options', chat=chat, kind='faqs', owner=chat.user, userMessage=False, is_new=is_new, is_additional=is_additional) if self.node_name_is_one_of('TITLE'): divider = ChatDivider(text=next_lesson.lesson.title, unitlesson=next_lesson) divider.save() message = Message.objects.get_or_create( contenttype='chatdivider', content_id=divider.id, input_type='custom', type='breakpoint', chat=chat, owner=chat.user, kind='message', is_additional=is_additional)[0] if self.node_name_is_one_of('START_MESSAGE',): message = Message.objects.create( input_type='options', text=self.title, chat=chat, owner=chat.user, kind='button', is_additional=is_additional) if self.node_name_is_one_of('DIVIDER'): divider = ChatDivider(text=self.title) divider.save() message = Message.objects.get_or_create( contenttype='chatdivider', content_id=divider.id, input_type='custom', type='breakpoint', chat=chat, owner=chat.user, kind='message', is_additional=is_additional)[0] if self.node_name_is_one_of('START') and self.fsm.fsm_name_is_one_of('live_chat'): message = Message.objects.get_or_create( chat=chat, text=self.title, kind='button', is_additional=is_additional, owner=chat.user, )[0] if self.name in ( 'GET_UNIT_NAME_TITLE', 'GET_UNIT_QUESTION', 'GET_UNIT_ANSWER', 'GET_HAS_UNIT_ANSWER', ): _data = dict( chat=chat, owner=chat.user, input_type='text', kind='response', userMessage=True, is_additional=is_additional ) if isinstance(current, UnitLesson): _data['content_id'] = current.id # _data['text'] = current.lesson.title _data['contenttype'] = 'unitlesson' elif message and message.content: # _data['text'] = "current.lesson" _data['content_id'] = message.content_id _data['contenttype'] = message.contenttype # content_id = current.id if current else None message = Message.objects.create(**_data) if self.name in ('HAS_UNIT_ANSWER', 'WELL_DONE'): text = "**{}** \n\n{}".format(self.title, getattr(self, 'help', '') or '') _data = dict( chat=chat, text=text, input_type='options', kind='message', owner=chat.user, userMessage=False, is_additional=is_additional ) if message and message.content_id: _data['content_id'] = message.content_id _data['contenttype'] = 'unitlesson' message = Message.objects.create(**_data) if self.name in ('WELL_DONE', 'ASK_NEW_FAQ', 'GET_NEW_FAQ'): text = "**{}** \n\n{}".format(self.title, getattr(self, 'help', '') or '') _data = dict( chat=chat, text=text, input_type='options', kind='button', owner=chat.user, userMessage=False, is_additional=is_additional ) if message and message.content_id: _data['content_id'] = message.content_id _data['contenttype'] = 'unitlesson' message = Message.objects.create(**_data) if self.name in ('ADDING_FAQ',): ul_id = c_chat_context().find_one({"chat_id": chat.id}).get('actual_ul_id') unitLesson = UnitLesson.objects.filter(id=ul_id).first() faq_response = Response.objects.create( unitLesson=unitLesson, lesson=unitLesson.lesson, kind=Response.STUDENT_QUESTION, course=self.load_json_id_dict(chat.state.data).get('course'), is_preview=chat.enroll_code.isPreview, is_test=chat.enroll_code.isTest, author=chat.user, needsEval=True) c_chat_stack().update_one( {"stack_id": faq_response_pattern}, {"$push": {"stack": faq_response.id}}, upsert=True) _data = dict( chat=chat, text=self.title, input_type='options', kind='button', owner=chat.user, userMessage=False, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ( 'NEW_FAQ_TITLE', ): ul_id = c_chat_context().find_one({"chat_id": chat.id}).get('actual_ul_id') unitLesson = UnitLesson.objects.filter(id=ul_id).first() faq_response = Response.objects.create( unitLesson=unitLesson, lesson=unitLesson.lesson, kind=Response.STUDENT_QUESTION, course=self.load_json_id_dict(chat.state.data).get('course'), is_preview=chat.enroll_code.isPreview, is_test=chat.enroll_code.isTest, author=chat.user, needsEval=True) c_chat_stack().update_one( {"stack_id": faq_response_pattern}, {"$push": {"stack": faq_response.id}}, upsert=True) _data = dict( chat=chat, owner=chat.user, text=self.title, input_type='custom', kind='message', userMessage=False, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ( 'ASK_NEW_FAQ', ): _data = dict( chat=chat, owner=chat.user, text=self.title, input_type='custom', kind='message', userMessage=False, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ('WILL_TRY_MESSAGE', 'F**K', 'MSG_FOR_INQUIRY', 'WILL_TRY_MESSAGE_2', 'SELECT_NEXT_FAQ', 'WILL_TRY_MESSAGE_3', 'SHOW_FAQ_ANSWERS', 'INTRO_MSG'): text = None if self.name == 'SHOW_FAQ_ANSWERS': ul_id = c_chat_context().find_one({"chat_id": chat.id}).get('actual_ul_id') actual_faq_id = c_chat_context().find_one( {"chat_id": chat.id}).get('actual_faq_id', None) faq_answers = c_faq_data().find_one( { "chat_id": chat.id, "ul_id": ul_id, "faqs.{}.answers.done".format(actual_faq_id): False} ).get('faqs').get(actual_faq_id).get('answers') for i in faq_answers: if not i.get('done'): answer = Response.objects.filter(id=i.get('answer_id')).first() text = "<b>{}</b><br>{}".format(answer.title, answer.text) if answer else None c_faq_data().update_one( { "chat_id": chat.id, "ul_id": ul_id, "faqs.{}.answers.answer_id".format(actual_faq_id): i.get('answer_id') }, {"$set": {"faqs.{}.answers.$.done".format(actual_faq_id): True}} ) break _data = dict( chat=chat, owner=chat.user, text=text or self.title, input_type='custom', kind='message', userMessage=False, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ( 'NEW_FAQ_DESCRIPTION', 'ASK_FOR_FAQ_ANSWER', 'ASK_UNDERSTANDING', 'FAQ' ): _data = dict( chat=chat, owner=chat.user, text=self.title, input_type='custom', kind='message', userMessage=False, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ('GET_NEW_FAQ_TITLE', 'GET_NEW_FAQ_DESCRIPTION'): if self.name == 'GET_NEW_FAQ_TITLE': try: faq_response_id = c_chat_stack().find_one({"stack_id": faq_response_pattern}).get('stack')[-1] except IndexError: faq_response_id = None else: document = c_chat_stack().find_and_modify( query={"stack_id": faq_response_pattern}, update={"$pop": {"stack": 1}}) stack = document.get('stack', []) faq_response_id = stack.pop() if stack else None _data = dict( contenttype='response', content_id=faq_response_id, chat=chat, input_type='text', kind='response', sub_kind='add_faq', owner=chat.user, userMessage=True, is_additional=is_additional ) message = Message.objects.create(**_data) if self.name in ('GET_NEW_FAQ',): _data = dict( input_type='options', chat=chat, owner=chat.user, kind='add_faq', sub_kind='add_faq', userMessage=True, is_additional=is_additional, ) message = Message.objects.create(**_data) if self.name in ('GET_FOR_FAQ_ANSWER',): _data = dict( input_type='options', chat=chat, owner=chat.user, kind='get_faq_answer', sub_kind='get_faq_answer', userMessage=True, is_additional=is_additional, ) message = Message.objects.create(**_data) if self.name in ('SHOW_FAQ_BY_ONE',): ul_id = c_chat_context().find_one({"chat_id": chat.id}).get('actual_ul_id') try: # TODO change to the Assignment expressions in Python3.8 faq_data = c_faq_data().find_one({"chat_id": chat.id, "ul_id": ul_id}) faqs = faq_data.get('faqs', {}) if faq_data else {} faq_id = None for key, value in list(faqs.items()): if not value.get('status').get('done', False): faq_id = key break except IndexError: faq_id = None if faq_id: c_faq_data().update_one( {"chat_id": chat.id, "ul_id": ul_id}, {"$set": {"faqs.{}.status.done".format(faq_id): True}}) c_chat_context().update_one( {"chat_id": chat.id}, {"$set": {"actual_faq_id": faq_id}}, upsert=True ) _data = dict( contenttype='response', content_id=int(faq_id), kind='response', sub_kind='faq', chat=chat, owner=chat.user, userMessage=False, is_additional=is_additional, ) message = Message.objects.create(**_data) if self.name in ('GET_UNDERSTANDING',): _data = dict( kind='ask_faq_understanding', input_type='options', chat=chat, owner=chat.user, userMessage=True, is_additional=is_additional, ) message = Message.objects.create(**_data) # wait for RECYCLE node and any node starting from WAIT_ except WAIT_ASSESS if is_wait_node(self.name): lookup = dict( chat=chat, text=self.title, kind='button', is_additional=False, owner=chat.user ) message = Message.objects.get_or_create(**lookup)[0] return message
def perform_update(self, serializer): chat_id = self.request.data.get('chat_id') message = self.get_object() node = message.chat.state.fsmNode chat = Chat.objects.get(id=chat_id, user=self.request.user) activity = chat.state and chat.state.activity def is_in_node(node: str) -> bool: return message.chat.state.fsmNode.name == node # Check if message is not in current chat if not message.chat or message.chat != chat: return # Do procced the user response in the FSMNode if hasattr(node._plugin, 'handler'): node._plugin.handler(message, chat, self.request, self.next_handler) return if is_chat_add_faq(message) and is_in_node('GET_NEW_FAQ'): message.text = self.request.data.get('option', 'no') message.save() chat.last_modify_timestamp = timezone.now() chat.save() if message.input_type == 'text' and not message.sub_kind == 'add_faq': message.chat = chat text = self.request.data.get('text') if message.lesson_to_answer.sub_kind == Lesson.EQUATION: text = text.strip("$") text = '.. math:: ' + text resp = StudentResponse(text=text) # convert base64 attachment string to django File data_attachment = self.request.data.get('attachment') if data_attachment and data_attachment.startswith('data:image'): format, image_string = data_attachment.split(';base64,') extension = format.split('/')[-1].split('+')[0] name = '{}-{}.{}'.format('canvas', key_secret_generator(), extension) resp.attachment = ContentFile(base64.b64decode(image_string), name=name) if not message.content_id: resp.lesson = message.lesson_to_answer.lesson resp.unitLesson = message.lesson_to_answer resp.course = message.chat.enroll_code.courseUnit.course resp.author = self.request.user resp.activity = activity resp.is_test = chat.is_test resp.is_preview = chat.enroll_code.isPreview resp.sub_kind = resp.lesson.sub_kind else: resp = message.content resp.text = text resp.is_trial = chat.is_trial resp.save() if not message.timestamp: message.content_id = resp.id chat.next_point = message chat.last_modify_timestamp = timezone.now() chat.save() serializer.save(content_id=resp.id, timestamp=timezone.now(), chat=chat) else: serializer.save() message_is_response = message.contenttype == 'response' lesson_has_sub_kind = message.lesson_to_answer and message.lesson_to_answer.sub_kind content_is_not_additional = not message.content and not message.is_additional mc_selfeval = None if (message_is_response and lesson_has_sub_kind and content_is_not_additional) or ( message_is_response and message.lesson_to_answer and message.lesson_to_answer.sub_kind == 'choices' and not content_is_not_additional): resp_text = '' if message.lesson_to_answer.sub_kind == Lesson.MULTIPLE_CHOICES: selected_items = self.request.data.get('selected') try: selected = selected_items[str(message.id)]['choices'] except KeyError: # here request.data is like {"option":1,"chat_id":9,"selected":{"116":{"choices":[0]}}} selected_msg_ids = list( self.request.data.get('selected').keys()) # selected_messages == tuple with keys of this dict {"116":{"choices":[0]}} - it will be ("116",) msg_ids = Message.objects.filter(id__in=selected_msg_ids, chat=chat).values_list( 'id', flat=True) correct_ids = set(msg_ids).intersection( set(int(i) for i in list(selected_items.keys()))) selected_choices = [] for i in correct_ids: selected_choices.append( selected_items[str(i)]['choices']) selected = chain(*selected_choices) resp_text = '[selected_choices] ' + ' '.join( str(i) for i in selected) correct_choices = set([ _[0] for _ in message.lesson_to_answer.lesson.get_correct_choices() ]) selected_choices = set([_ for _ in chain(*selected_choices)]) if not (correct_choices - selected_choices or correct_choices ^ selected_choices): mc_selfeval = StudentResponse.CORRECT elif selected_choices & correct_choices: mc_selfeval = StudentResponse.CLOSE else: mc_selfeval = StudentResponse.DIFFERENT resp = StudentResponse(text=resp_text) # tes, preview flags resp.is_test = chat.is_test resp.selfeval = mc_selfeval or None resp.is_preview = chat.enroll_code.isPreview resp.is_trial = chat.is_trial resp.kind = message.lesson_to_answer.kind resp.sub_kind = message.lesson_to_answer.sub_kind resp.lesson = message.lesson_to_answer.lesson resp.unitLesson = message.lesson_to_answer resp.course = message.chat.enroll_code.courseUnit.course resp.author = self.request.user resp.activity = activity resp.save() if not message.timestamp: serializer.save(content_id=resp.id, timestamp=timezone.now(), chat=chat, response_to_check=resp) else: serializer.save() return if (is_in_node('GET_NEW_FAQ_TITLE') or is_in_node('GET_NEW_FAQ_DESCRIPTION')) \ and message_is_response and message.sub_kind == 'add_faq': text = self.request.data.get('text') faq_request = message.content if is_in_node('GET_NEW_FAQ_TITLE'): faq_request.title = text else: faq_request.text = text faq_request.save() faq_request.notify_instructors() message.text = text message.save() if is_in_node('GET_FOR_FAQ_ANSWER'): message.text = self.request.data.get('option', 'help') message.save() if is_in_node('GET_UNDERSTANDING'): message.text = self.request.data.get('option', 'no') message.save() if message.input_type == 'options' and message.kind != 'button': if (message.contenttype == 'uniterror' and 'selected' in self.request.data): # user selected error model message.chat = chat try: selected = self.request.data.get('selected')[str( message.id)]['errorModel'] except KeyError: selected = [] uniterror = message.content uniterror.save_response(user=self.request.user, response_list=selected) message.text = message.get_html() message.save() if not message.chat.is_live: get_additional_messages(uniterror.response, chat, selected) chat.next_point = self.next_handler.next_point( current=message.content, chat=chat, message=message, request=self.request) chat.last_modify_timestamp = timezone.now() chat.save() serializer = self.get_serializer(message, data=self.request.data) serializer.is_valid() serializer.save(chat=chat) elif (message.kind == 'add_faq' and message.sub_kind == 'add_faq') or \ (message.kind == 'get_faq_answer' and message.sub_kind == 'get_faq_answer') or \ (message.kind == 'ask_faq_understanding'): pass elif (message.contenttype == 'unitlesson' and message.kind == 'faqs' and 'selected' in self.request.data): message.chat = chat try: selected = self.request.data.get('selected')[str( message.id)]['faqModel'] except KeyError: selected = [] if selected: c_faq_data().update_one( { "chat_id": chat.id, "ul_id": message.content.id }, { "$set": { "faqs": { str(faq_id): { 'status': { "done": False } } for faq_id in selected } } }) # Save presented in message FAQs to avoid futher bugs in wrong dadta selection message.text = message.get_html() message.save() chat.next_point = self.next_handler.next_point( current=message.content, chat=chat, message=message, request=self.request) chat.last_modify_timestamp = timezone.now() chat.save() serializer = self.get_serializer(message, data=self.request.data) serializer.is_valid() serializer.save(chat=chat) elif message.contenttype == 'NoneType' and message.kind == 'abort': # user selected abort model message.chat = chat try: selected = self.request.data.get('selected')[str( message.id)]['errorModel'] except KeyError: selected = [] if not message.chat.is_live and selected: get_help_messages(chat) chat.next_point = self.next_handler.next_point( current=message.content, chat=chat, message=message, request=self.request) chat.last_modify_timestamp = timezone.now() chat.save() serializer.save(chat=chat) elif message.content_id and not message.student_error: # confidence and selfeval message.chat = chat opt_data = self.request.data.get('option') resp = message.content if chat.state and chat.state.fsmNode.node_name_is_one_of( 'GET_CONFIDENCE', 'ADDITIONAL_GET_CONFIDENCE'): resp.confidence = opt_data text = resp.get_confidence_display() else: resp.selfeval = opt_data text = resp.get_selfeval_display() # FIX if response was correct - user will not go to `else` section and response status should be set if not resp.is_locked: resp.status = EVAL_TO_STATUS_MAP.get(opt_data) message.text = text resp.save() chat.next_point = message chat.last_modify_timestamp = timezone.now() chat.save() serializer.save(content_id=resp.id, chat=chat, text=text) else: # message.chat = chat selfeval = self.request.data.get('option') resp = message.student_error resp.status = selfeval resp.save() # CRITICAL note - do not override response status # pass status to main response ONLY in case of absence the status at all # is_locked status is setted in TRANSITION node in chat FSM if not resp.response.is_locked: resp.response.status = selfeval resp.response.save() chat.next_point = message chat.last_modify_timestamp = timezone.now() chat.save() message.text = selfeval message.save() serializer.save(text=selfeval, chat=chat) if message.kind == 'button' and not (message.content_id and message.content and message.content.sub_kind): chat.last_modify_timestamp = timezone.now() chat.next_point = self.next_handler.next_point( current=message.content, chat=chat, message=message, request=self.request, ) chat.save()