def next_lesson_after_title(self, edge, fsmStack, request, useCurrent=False, **kwargs): """ Edge method that moves us to right state for next lesson (or END). """ fsm = edge.fromNode.fsm unitStatus = fsmStack.state.get_data_attr('unitStatus') nextUL = unitStatus.get_lesson() answer = nextUL.get_answers().first() # Update chat context # Don't ask chat = fsmStack c_chat_context().update_one({"chat_id": chat.id}, { "$set": { "actual_ul_id": answer.id if answer else nextUL.id, "thread_id": nextUL.id, f"activity.{nextUL.id}": timezone.now(), "need_faqs": False } }, upsert=True) if nextUL.is_question(): return fsm.get_node(name='ASK') else: # just a lesson to read return edge.toNode
def check_selfassess_and_next_lesson(self, edge, fsmStack, request, useCurrent=False, **kwargs): fsm = edge.fromNode.fsm if fsmStack.state.unitLesson.lesson.enable_auto_grading and not fsmStack.state.fsmNode.name == 'GRADING': return fsm.get_node('GRADING') if fsmStack.next_point.content.selfeval != 'correct': if (fsmStack.next_point.content.unitLesson.get_errors() or fsmStack.next_point.content.lesson.add_unit_aborts and fsmStack.next_point.content.unitLesson.unit.get_aborts()): c_chat_context().update_one({"chat_id": fsmStack.id}, {"$set": { "need_faqs": True }}, upsert=True) return fsm.get_node('ERRORS') else: resp = fsmStack.next_point.content resp.status = 'help' resp.save() return fsm.get_node('FAQ') return edge.toNode
def next_edge(self, edge, fsmStack, request, useCurrent=False, **kwargs): fsm = edge.fromNode.fsm inquiry_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_inquiry_id') if inquiry_id: ob = InquiryCount.objects.filter(id=inquiry_id).first() ob.status = fsmStack.next_point.text.lower() ob.save() # Defaul value - go to asking new faq from Student next_node = edge.toNode if fsmStack.next_point.text.lower() == 'help': next_node = fsm.get_node('WILL_TRY_MESSAGE_3') else: ul_id = c_chat_context().find_one({ "chat_id": fsmStack.id }).get('actual_ul_id') 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 _update_thread_id(self, chat, thread_id): c_chat_context().update_one( {"chat_id": chat.id}, {"$set": { "thread_id": thread_id, }}, upsert=True )
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 update_activity(self, chat_id: int, thread_id: int) -> None: c_chat_context().update_one( {"chat_id": chat_id}, {"$set": { "thread_id": thread_id, f"activity.{thread_id}": timezone.now(), "need_faqs": False }}, upsert=True )
def next_edge(self, edge, fsmStack, request, useCurrent=False, **kwargs): fsm = edge.fromNode.fsm if (fsmStack.next_point.content.unitLesson.get_errors() or fsmStack.next_point.content.lesson.add_unit_aborts and fsmStack.next_point.content.unitLesson.unit.get_aborts()): c_chat_context().update_one({"chat_id": fsmStack.id}, {"$set": { "need_faqs": True }}, upsert=True) return fsm.get_node('ERRORS') else: return fsm.get_node('FAQ')
def update_activity(chat_id: int) -> None: """ Update chat context for a currently active thread. """ context = c_chat_context().find_one({"chat_id": chat_id}) if context and context.get('thread_id'): thread_id = context.get('thread_id') c_chat_context().update_one( {"chat_id": chat_id}, {"$set": { f"activity.{thread_id}": timezone.now() }}, )
def get(self, request, pk=None): chat_id = self.request.GET.get('chat_id') try: chat = self.validate_and_get_chat(chat_id) except ValidationError as e: return Response({'errors': str(e)}) self.check_object_permissions(self.request, chat) if chat.state and chat.state.fsmNode.fsm.name == 'updates': return Response({ 'error': 'You are in the middle of the another updates review process' }) unitlesson = get_object_or_404(UnitLesson, pk=pk) if chat.state: saved_actual_ul = c_chat_context().find_one({ "chat_id": chat.id }).get('actual_ul_id') thread = c_chat_context().find_one({ "chat_id": chat.id }).get('thread_id') chat.state.set_data_attr('saved_next_point', chat.next_point.id) chat.state.set_data_attr('saved_actual_ul', saved_actual_ul) chat.state.set_data_attr('thread', thread) chat.state.save_json_data() divider = ChatDivider(text=unitlesson.lesson.title, unitlesson=unitlesson) divider.save() m = Message.objects.get_or_create(contenttype='chatdivider', content_id=divider.id, input_type='custom', type='breakpoint', thread_id=pk, chat=chat, owner=chat.user, kind='message', is_additional=True)[0] chat.next_point = self.next_handler.next_point(current=unitlesson, chat=chat, message=m, request=request, updates=True) chat.save() serializer = MessageSerializer(m) return Response(serializer.data)
def next_additional_lesson(self, edge, fsmStack, request, useCurrent=False, **kwargs): """ Edge method that moves us to right state for next lesson (or END). """ fsm = edge.fromNode.fsm _status = fsmStack.next_point.student_error.status if _status == NEED_HELP_STATUS: additionals = Message.objects.filter(is_additional=True, chat=fsmStack, timestamp__isnull=True) elif _status in [NEED_REVIEW_STATUS, DONE_STATUS]: if _status == DONE_STATUS: c_chat_context().update_one({"chat_id": fsmStack.id}, {"$set": { "need_faqs": False }}) Message.objects.filter(student_error=fsmStack.next_point.student_error, is_additional=True, chat=fsmStack, timestamp__isnull=True).delete() additionals = Message.objects.filter(is_additional=True, chat=fsmStack, timestamp__isnull=True) if additionals: next_message = additionals.order_by('student_error').first() fsmStack.state.unitLesson = next_message.content if next_message.student_error != fsmStack.next_point.student_error: return fsm.get_node( 'NEED_HELP_MESSAGE' ) if _status == NEED_HELP_STATUS else fsm.get_node('STUDENTERROR') if fsmStack.state.unitLesson.lesson.kind in ('orct', 'choices'): return fsm.get_node('ORCT_LETS_START_MESSAGE') else: return fsm.get_node('NEED_HELP_MESSAGE' ) if _status == NEED_HELP_STATUS else fsm.get_node( 'END') return edge.toNode
def next_lesson_after_errors(self, edge, fsmStack, request, useCurrent=False, **kwargs): """ Edge method that moves us to right state for next lesson (or END). """ fsm = edge.fromNode.fsm if c_chat_context().find_one({"chat_id": fsmStack.id}).get('need_faqs'): return fsm.get_node('FAQ') return edge.toNode
def get_message(self, chat, next_lesson, is_additional, *args, **kwargs) -> Message: data = chat.state.load_json_data() if any(('em_resolutions' in data, 'faq_answers' in data, 'new_ems' in data, 'new_faqs' in data)): text = 'There are new updates for a Thread you asked for a help.' c_chat_context().update_one( {"chat_id": chat.id}, {"$set": {"actual_ul_id": chat.state.unitLesson.id}} ) else: text = 'I can\'t find updates for you.' _data = { 'chat': chat, 'text': text, 'owner': chat.user, 'input_type': 'custom', 'kind': 'message', 'is_additional': is_additional } message = Message(**_data) message.save() return message
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 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') 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 return fsm.get_node( 'SHOW_FAQ_BY_ONE') if show_another_faq else edge.toNode
def collect_updates(self, node, fsmStack, request, **kwargs): # TODO add unittests chat = kwargs.get('chat') unit_lesson = kwargs.get('unitlesson') response = chat.message_set.filter( lesson_to_answer_id=unit_lesson.id, kind='response', contenttype='response', content_id__isnull=False).first().content affected_ems = [i.errorModel for i in response.studenterror_set.all()] context = c_chat_context().find_one({"chat_id": chat.id}) last_access_time = context.get('activity', {}).get(f"{unit_lesson.id}") if context else None tz_aware_datetime = ( last_access_time.replace(tzinfo=tz.tzutc()) if last_access_time else chat.last_modify_timestamp.replace(tzinfo=tz.tzutc())) # Collect EMs resolutions. Don't filter by user em_resolutions = unit_lesson.em_resolutions(tz_aware_datetime, affected_ems) fsmStack.state.set_data_attr('em_resolutions', em_resolutions) if em_resolutions else None thread_answer = unit_lesson.get_answers().first() interested_faqs = thread_answer.response_set.filter( Q( kind=Response.STUDENT_QUESTION, inquirycount__addedBy=request.user ) | Q(author=request.user, kind=Response.STUDENT_QUESTION)) # Collect FAQ answers. Do filter by user as well as by applied previously FAQs faq_answers = unit_lesson.faq_answers(tz_aware_datetime, request.user, interested_faqs) fsmStack.state.set_data_attr('faq_answers', faq_answers) if faq_answers else None # Collect new EMs. Don't filter by user new_ems = unit_lesson.new_ems(tz_aware_datetime) fsmStack.state.set_data_attr('new_ems', new_ems) if new_ems else None # Collect ne FAQs. Do filter by user new_faqs = unit_lesson.new_faqs(tz_aware_datetime, request.user) fsmStack.state.set_data_attr('new_faqs', new_faqs) if new_faqs else None
def save(self, *args, **kwargs): context = c_chat_context().find_one({"chat_id": self.chat_id }) or dict() if not (self.pk or self.thread_id): self.thread_id = context.get('thread_id') super().save(*args, **kwargs)
def next_point(self, current, chat, message, request, resources=False, updates=False): next_point = None additionals = Message.objects.filter(is_additional=True, chat=chat, student_error__isnull=False, timestamp__isnull=True) helps = Message.objects.filter(is_additional=True, chat=chat, kind='abort', timestamp__isnull=True) if chat.state and chat.state.fsmNode.node_name_is_one_of('END'): if chat.state.fsmNode.fsm.fsm_name_is_one_of('faq'): self.pop_state(chat) edge = chat.state.fsmNode.outgoing.get(name='next') chat.state.fsmNode = edge.transition(chat, request) chat.state.save() saved_actual_ul = (chat.state.get_data_attr('saved_actual_ul') if 'saved_actual_ul' in chat.state.load_json_data() else None) c_chat_context().update_one( {"chat_id": chat.id}, {"$set": { "actual_ul_id": saved_actual_ul }}) if saved_actual_ul else None next_point = chat.state.fsmNode.get_message(chat, request, current=current, message=message) elif chat.state.fsmNode.fsm.fsm_name_is_one_of('updates'): self.pop_state(chat) if chat.state: edge = chat.state.fsmNode.outgoing.get(name='next') chat.state.fsmNode = edge.transition(chat, request) chat.state.save() next_point = chat.state.fsmNode.get_message( chat, request, current=current, message=message) else: self.pop_state(chat) if chat.state and chat.state.fsmNode.node_name_is_one_of('FAQ'): chat_context = c_chat_context().find_one({'chat_id': chat.id}) self.push_state( chat, request, 'faq', { 'unitlesson': UnitLesson.objects.filter( id=chat_context.get('actual_ul_id')).first(), 'chat': chat }) next_point = chat.state.fsmNode.get_message(chat, request, current=current, message=message) if chat.state and chat.state.fsmNode.node_name_is_one_of( 'FAQ_UPDATES'): saved_actual_ul = c_chat_context().find_one({ "chat_id": chat.id }).get('actual_ul_id') chat.state.set_data_attr('saved_actual_ul', saved_actual_ul) chat.state.save_json_data() thread_answer = chat.state.unitLesson.get_answers().first() self.push_state( chat, request, 'faq', { 'unitlesson': thread_answer, 'chat': chat, 'updates': True, 'new_faqs': (chat.state.get_data_attr('new_faqs') if 'new_faqs' in chat.state.load_json_data() else None) }) c_chat_context().update_one( {"chat_id": chat.id}, {"$set": { "actual_ul_id": thread_answer.id }}) next_point = chat.state.fsmNode.get_message(chat, request, current=current, message=message) elif helps and not chat.state.fsmNode.fsm.fsm_name_is_one_of('help'): unitlesson = helps.first().content self.push_state(chat, request, 'help', {'unitlesson': unitlesson}) next_point = chat.state.fsmNode.get_message(chat, request, current=current, message=message) elif additionals and not chat.state.fsmNode.fsm.fsm_name_is_one_of( 'additional'): unitlesson = additionals.order_by('student_error').first().content self.push_state(chat, request, 'additional', {'unitlesson': unitlesson}) next_point = chat.state.fsmNode.get_message(chat, request, current=current, message=message) elif resources: self.push_state(chat, request, 'resource', { 'unitlesson': current, 'chat': chat }) next_point = chat.state.fsmNode.get_message(chat, request) elif updates: self.push_state(chat, request, 'updates', { 'unitlesson': current, 'chat': chat }) next_point = self.next_point(current=current, chat=chat, message=message, request=request) elif chat.state and chat.state.fsmNode.node_name_is_one_of( 'VIEWUPDATES') and 'next_update' in chat.state.load_json_data( ) and chat.state.get_data_attr( 'next_update') and chat.state.get_data_attr( 'next_update').get('enabled'): unit_lesson_id = chat.state.get_data_attr('next_update').get( 'thread_id') chat.state.set_data_attr('next_update', None) chat.state.save_json_data() self.push_state( chat, request, 'updates', { 'unitlesson': UnitLesson.objects.filter(id=unit_lesson_id).first(), 'chat': chat }) next_point = self.next_point(current=current, chat=chat, message=message, request=request) elif chat.state: if not next_point: if not chat.state.fsmNode.node_name_is_one_of('END'): edge = chat.state.fsmNode.outgoing.get(name='next') chat.state.fsmNode = edge.transition(chat, request) chat.state.save() if not (chat.state.fsmNode.node_name_is_one_of( 'FAQ', 'VIEWUPDATES', 'UPDATES') or is_last_main_transition_wo_updates(chat.state) or is_update_transition_wo_updates_w_last_main( chat.state, chat) or is_end_update_node(chat.state) or (waffle.switch_is_active('compound_faq_answer') and chat.state.fsmNode.node_name_is_one_of('SHOW_FAQ'))): next_point = chat.state.fsmNode.get_message( chat, request, current=current, message=message) else: next_point = self.next_point(current=current, chat=chat, message=message, request=request) else: return None if not message.timestamp: message.timestamp = timezone.now() message.save() # TODO: move to external function group = True while group: if self.group_filter(message, next_point): if next_point.input_type in [ 'text', 'options' ] and next_point.sub_kind != 'faq': break next_point = self.next_point(current=next_point.content, chat=chat, message=next_point, request=request) else: group = False return next_point
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