示例#1
0
class UserTypes(db.Model):
    __tablename__ = 'user_types'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(
        db.Enum('student',
                'teacher',
                'data_operator',
                'intern',
                'institutes',
                name='user_types_enum'))
class PastExamResult(db.Model):
    __tablename__ = 'past_exam_results'
    __table_args__ = (db.UniqueConstraint('year', 'exam'), )
    id = db.Column(db.Integer, primary_key=True)
    year = db.Column(db.Integer)
    exam = db.Column(
        db.Enum(*app.config['TARGET_EXAMS'].keys(), name='target_exams_enum'))
    data = db.Column(
        JSON)  # json containing cutoff, marks-rank map, rank-college map

    @classmethod
    def insert(cls, year, exam, data):
        try:
            exam_result = cls(year=year, exam=exam, data=json.dumps(data))
            db.session.add(exam_result)
            db.session.commit()
        except IntegrityError:
            exam_result = cls.query.filter(cls.year == year,
                                           cls.exam == exam).first()
            exam_result.data = json.dumps(data)
            db.session.commit()
        return exam_result.id
示例#3
0
class Student(User, db.Model):
    __tablename__ = 'students'
    type = db.Column(db.Integer, db.ForeignKey('user_types.id'), nullable=False)
    mobile_no = db.Column(db.String(app.config['MOBILE_NO_MAX_LENGTH']), unique=True)
    city = db.Column(db.String(100))
    area = db.Column(db.String(100))
    pin = db.Column(db.String(10))
    school = db.Column(db.String(100))
    ntse_score = db.Column(db.Float)
    roll_no = db.Column(db.String(20))
    branches = db.Column(ARRAY(db.String(1)))
    target_exams = db.Column(ARRAY(db.String(1)))
    target_exam_roll_nos = db.Column(ARRAY(db.String(100)))
    target_year = db.Column(db.Integer)
    father_name = db.Column(db.String(app.config['NAME_MAX_LENGTH']))
    father_mobile_no = db.Column(db.String(app.config['MOBILE_NO_MAX_LENGTH']))
    father_email = db.Column(db.String(app.config['EMAIL_MAX_LENGTH']))
    payment_plan_id = db.Column(db.Integer, db.ForeignKey('payment_plans.id'))
    registered_from = db.Column(db.Enum('institute', 'independent', name='registered_from_enum'))
    fp_token = db.Column(db.String(64))
    refcode = db.Column(db.String(200), nullable=True)

    @classmethod
    def create(cls, name, email, password, mobile_no=None, city=None, area=None, pin=None, school=None, ntse_score=None,
               roll_no=None, branches=None, target_exams=None, target_exam_roll_nos=None, target_year=None, father_name=None,
               father_mobile_no=None, father_email=None, payment_plan_id=None, registered_from=None, refcode=None):
        """

        :param name:
        :param email:
        :param password:
        :param mobile_no:
        :param city:
        :param area:
        :param pin:
        :param school:
        :param ntse_score:
        :param roll_no:
        :param branches:
        :param target_exams:
        :param target_exam_roll_nos:
        :param target_year:
        :param father_name:
        :param father_email:
        :param father_mobile_no:
        :param payment_plan_id:
        :param registered_from:
        :return:
        """
        user_type = UserTypes.query.filter_by(name='student').first()
        student = cls(name=name, email=email, password=password, mobile_no=mobile_no, city=city, area=area, pin=pin,
                      school=school, ntse_score=ntse_score, roll_no=roll_no, branches=branches, target_exams=target_exams,
                      target_exam_roll_nos=target_exam_roll_nos, target_year=target_year, father_name=father_name,
                      father_mobile_no=father_mobile_no, payment_plan_id=payment_plan_id, registered_from=registered_from,
                      type=user_type.id, refcode=refcode)
        db.session.add(student)
        try:
            db.session.commit()
        except IntegrityError as e:
            db.session.rollback()
            if 'email' in e.message:
                raise EmailAlreadyRegistered
            if 'mobile_no' in e.message:
                raise MobileNoAlreadyRegistered
            raise e
        return student

    @classmethod
    def get(cls, id):
        """
        Get details of a single student

        :param id: student id
        :return: student object
        """
        student = cls.query.get(id)
        if student is not None:
            return student
        else:
            raise InvalidStudentId

    @classmethod
    def get_list(cls, page=1, limit=10):
        """
        Get a list of students

        :param page:
        :param limit:
        :return:
        """
        students_pag_obj = cls.query.filter_by(is_active=True).paginate(page, limit)
        students = students_pag_obj.items
        total = students_pag_obj.total

        return students, total

    @classmethod
    def get_attempted_mock_tests(cls, id):
        """

        :param id:
        :return:
        """
        attempted_mock_tests = AttemptedMockTest.query.filter_by(student_id=id).all()
        return attempted_mock_tests

    @classmethod
    def get_pushed_mock_tests(cls, id):
        """

        :param id:
        :return:
        """
        # get batches that the student is currently part of
        student_current_batches = StudentBatches.query.filter(StudentBatches.student_id == id, StudentBatches.left_at == None).all()
        if len(student_current_batches) > 0:
            # get mock tests that are pushed to batches that the student is currently part of and have not expired
            batch_ids = [sb.batch_id for sb in student_current_batches]

            pushed_mock_tests = PushedMockTest.query.filter(PushedMockTest.batch_id.in_(batch_ids),
                                                        or_(PushedMockTest.expires_at == None, PushedMockTest.expires_at > datetime.datetime.utcnow())).all()
        else:
            pushed_mock_tests = []

        return pushed_mock_tests
class Question(db.Model):
    __tablename__ = 'questions'
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text, nullable=False)
    status = db.Column(MutableDict.as_mutable(HSTORE),
                       default={})  # contains flags with values 0 and 1
    comprehension_id = db.Column(
        db.Integer, db.ForeignKey('comprehensions.id', ondelete='CASCADE'))
    all_options = db.Column(ARRAY(db.Text), default=[])
    correct_options = db.Column(ARRAY(db.Integer), default=[])
    option_reasons = db.Column(ARRAY(db.Text), default=[])
    average_time = db.Column(db.Integer)
    ontology = db.Column(ARRAY(db.Integer))
    nature = db.Column(
        db.Enum(*app.config['QUESTION_NATURE'].keys(),
                name='question_nature_enum'))
    difficulty = db.Column(
        db.Enum(*app.config['QUESTION_DIFFICULTY_LEVEL'],
                name='question_difficulty_enum'))
    type = db.Column(
        db.Enum(*app.config['QUESTION_TYPE'], name='question_type_enum'))
    text_solution = db.Column(db.Text)  # null until approved
    video_solution_url = db.Column(db.String(
        app.config['URL_MAX_LENGTH']))  # null until approved
    text_solution_by_type = db.Column(db.Integer,
                                      db.ForeignKey('user_types.id'))
    text_solution_by_id = db.Column(db.Integer)
    video_solution_by_type = db.Column(db.Integer,
                                       db.ForeignKey('user_types.id'))
    video_solution_by_id = db.Column(db.Integer)
    similar_question_ids = db.Column(ARRAY(db.Integer))  # null if unfilled
    created_by_type = db.Column(db.Integer, db.ForeignKey('user_types.id'))
    created_by_id = db.Column(db.Integer)
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    is_similarity_marked = db.Column(db.Boolean, default=False)

    @validates('video_solution_url')
    def validate_video_url(self, key, url):
        if url is None:
            return url
        parsed_url = urlparse.urlparse(url)
        netloc = parsed_url.netloc
        if netloc not in ('youtube.com', 'www.youtube.com', 'youtu.be',
                          'www.youtu.be'):
            raise UnAcceptableVideoUrl
        return url

    img_base64_pat = re.compile('<img [^>]*?src="(data:image.*?)".*?>')

    @classmethod
    def parse_content(cls, content):
        """
        Parse and return the content. Convert base64 images to s3 image urls.

        :param content:
        :return: string
        """
        match = cls.img_base64_pat.search(content)
        s3obj = S3()
        while match is not None:
            mimetype, image_data = parse_base64_string(match.groups()[0])
            url = s3obj.upload(image_data, mimetype)
            img_tag = '<img src="%s" />' % url
            content = content[:match.start()] + img_tag + content[match.end():]
            match = cls.img_base64_pat.search(content)

        return content

    @classmethod
    def create(cls,
               content,
               status,
               comprehension_id=None,
               all_options=None,
               correct_options=None,
               option_reasons=None,
               ontology_id=None,
               average_time=app.config['DEFAULT_AVERAGE_TIME'],
               nature=None,
               difficulty=None,
               type=None,
               text_solution=None,
               video_solution_url=None,
               text_solution_by_type=None,
               text_solution_by_id=None,
               video_solution_by_type=None,
               video_solution_by_id=None,
               similar_question_ids=None,
               created_by_type=None,
               created_by_id=None):
        """
        Create a new question and return the newly created object.

        :param content: question test
        :param status: status dict
        :param comprehension_id: if question is part of comprehension
        :param all_options:
        :param correct_options: indices of correct options
        :param option_reasons:
        :param ontology_id:
        :param average_time:
        :param nature:
        :param difficulty:
        :param type:
        :param text_solution:
        :param video_solution_url:
        :param text_solution_by_type: the type of user if text solution was provided
        :param text_solution_by_id:   the id of user if text solution was provided
        :param video_solution_by_type: the type of user if video solution was provided
        :param video_solution_by_id:    the id of user if video solution was provided
        :param similar_question_ids:    array of question ids
        :param created_by_type:     the type of user who entered this question
        :param created_by_id:       the id of user who entered this question
        :return:
        """
        if ontology_id is not None:
            ontology_obj = Ontology.query.get(ontology_id)
            if ontology_obj is None:
                raise InvalidOntologyNodeId
            ontology = ontology_obj.absolute_path
        else:
            ontology = None

        content = content.strip()
        content = cls.parse_content(content)

        if all_options is not None:
            for i, option in enumerate(all_options):
                if option is not None:
                    all_options[i] = cls.parse_content(option)

        if option_reasons is not None:
            for i, reason in enumerate(option_reasons):
                if reason is not None:
                    option_reasons[i] = cls.parse_content(reason)

        if text_solution is not None:
            text_solution = cls.parse_content(text_solution)

        question = cls(content=content,
                       status=status,
                       comprehension_id=comprehension_id,
                       all_options=all_options,
                       correct_options=correct_options,
                       option_reasons=option_reasons,
                       ontology=ontology,
                       average_time=average_time,
                       nature=nature,
                       difficulty=difficulty,
                       type=type,
                       text_solution=text_solution,
                       video_solution_url=video_solution_url,
                       text_solution_by_type=text_solution_by_type,
                       text_solution_by_id=text_solution_by_id,
                       video_solution_by_type=video_solution_by_type,
                       video_solution_by_id=video_solution_by_id,
                       similar_question_ids=similar_question_ids,
                       created_by_type=created_by_type,
                       created_by_id=created_by_id)
        db.session.add(question)
        db.session.commit()
        return question

    @classmethod
    def get(cls, id):
        """
        Get details of a single question

        :param id: question id
        :return: question object
        """
        question = cls.query.get(id)
        if question is not None:
            if question.text_solution is not None or question.video_solution_url is not None:
                ss = SolutionSubmission.query.filter_by(
                    question_id=question.id).all()
                for item in ss:
                    if item.solution_type == 'video':
                        video_submission = item
                    if item.solution_type == 'text':
                        text_submission = item
                if question.text_solution is not None:
                    question.text_solution_by_type = text_submission.submitted_by_type
                    question.text_solution_by_id = text_submission.submitted_by_id
                if question.video_solution_url is not None:
                    question.video_solution_by_type = video_submission.submitted_by_type
                    question.video_solution_by_id = video_submission.submitted_by_id
            return question
        else:
            raise InvalidQuestionId

    def update(self,
               content,
               status,
               comprehension_id=None,
               all_options=None,
               correct_options=None,
               option_reasons=None,
               ontology_id=None,
               average_time=app.config['DEFAULT_AVERAGE_TIME'],
               nature=None,
               difficulty=None,
               type=None,
               text_solution=None,
               video_solution_url=None,
               text_solution_by_type=None,
               text_solution_by_id=None,
               video_solution_by_type=None,
               video_solution_by_id=None,
               similar_question_ids=None):
        """
        Update the details of single question

        :param id: question_id
        :param content:
        :param status: status dict
        :param comprehension_id: if question is part of comprehension
        :param all_options:
        :param correct_options: indices of correct options
        :param option_reasons:
        :param ontology_id:
        :param average_time:
        :param nature:
        :param difficulty:
        :param type:
        :param text_solution:
        :param video_solution_url:
        :param text_solution_by_type: the type of user if text solution was provided
        :param text_solution_by_id:   the id of user if text solution was provided
        :param video_solution_by_type: the type of user if video solution was provided
        :param video_solution_by_id:    the id of user if video solution was provided
        :param similar_question_ids:    array of question ids
        :return:
        """
        if ontology_id is not None:
            ontology_obj = Ontology.query.get(ontology_id)
            if ontology_obj is None:
                raise InvalidOntologyNodeId
            ontology = ontology_obj.absolute_path
        else:
            ontology = None

        content = self.__class__.parse_content(content)

        if all_options is not None:
            for i, option in enumerate(all_options):
                if option is not None:
                    all_options[i] = self.__class__.parse_content(option)

        if option_reasons is not None:
            for i, reason in enumerate(option_reasons):
                if reason is not None:
                    option_reasons[i] = self.__class__.parse_content(reason)

        if text_solution is not None:
            text_solution = self.__class__.parse_content(text_solution)

        question = self
        question.content = content
        question.status = status
        question.comprehension_id = comprehension_id
        question.all_options = all_options
        question.correct_options = correct_options
        question.option_reasons = option_reasons
        question.ontology = ontology
        question.average_time = average_time
        question.nature = nature
        question.difficulty = difficulty
        question.type = type
        question.text_solution = text_solution
        question.video_solution_url = video_solution_url
        question.text_solution_by_type = text_solution_by_type
        question.text_solution_by_id = text_solution_by_id
        question.video_solution_by_type = video_solution_by_type
        question.video_solution_by_id = video_solution_by_id
        question.similar_question_ids = similar_question_ids
        db.session.commit()
        return question

    @classmethod
    def get_filtertered_list(cls,
                             nature=None,
                             type=None,
                             difficulty=None,
                             average_time=None,
                             categorized=None,
                             proof_read_categorization=None,
                             proof_read_text_solution=None,
                             proof_read_video_solution=None,
                             finalized=None,
                             error_reported=None,
                             text_solution_added=None,
                             video_solution_added=None,
                             ontology=None,
                             is_comprehension=None,
                             similar_questions_to=None,
                             not_similar_questions_to=None,
                             exclude_question_ids=None,
                             include_question_ids=None,
                             page=1,
                             limit=None):
        """
        Get a list of question after applying filters

        :param nature:
        :param type:
        :param difficulty:
        :param average_time:
        :param categorized:
        :param proof_read_categorization:
        :param proof_read_text_solution:
        :param proof_read_video_solution:
        :param finalized:
        :param text_solution_added:
        :param video_solution_added:
        :param ontology:
        :param is_comprehension:
        :param similar_questions_to: questions which are similar to the question id
        :param not_similar_questions_to: questions which are not similar to the question id
        :param exclude_question_ids: dont return questions with these ids
        :param include_question_ids: return questions with these ids
        :param page:
        :param limit:
        :return:
        """

        exprs = []

        if nature is not None:
            exprs.append(Question.nature == nature)

        if type is not None:
            exprs.append(Question.type == type)

        if difficulty is not None:
            exprs.append(Question.difficulty == difficulty)

        if average_time is not None:
            exprs.append(Question.average_time == average_time)

        if categorized is not None:
            exprs.append(Question.status['categorized'] == categorized)

        if proof_read_categorization is not None:
            exprs.append(Question.status['proof_read_categorization'] ==
                         proof_read_categorization)

        if proof_read_text_solution is not None:
            exprs.append(Question.status['proof_read_text_solution'] ==
                         proof_read_text_solution)

        if proof_read_video_solution is not None:
            exprs.append(Question.status['proof_read_video_solution'] ==
                         proof_read_video_solution)

        if finalized is not None:
            exprs.append(Question.status['finalized'] == finalized)

        if error_reported is not None:
            exprs.append(Question.status['error_reported'] == error_reported)

        if ontology is not None:
            exprs.append(Question.ontology.contains(ontology))

        if is_comprehension is not None:
            if is_comprehension == 0:
                exprs.append(Question.comprehension_id == None)
            else:
                exprs.append(Question.comprehension_id != None)

        if text_solution_added is not None:
            exprs.append(Question.status['text_solution_added'] == str(
                text_solution_added))

        if video_solution_added is not None:
            exprs.append(Question.status['video_solution_added'] == str(
                video_solution_added))

        if exclude_question_ids is not None:
            exprs.append(not_(Question.id.in_(exclude_question_ids)))

        if include_question_ids is not None:
            exprs.append(Question.id.in_(include_question_ids))

        if similar_questions_to is not None or not_similar_questions_to is not None:
            # Need to select only categorized questions
            categorized_expr = Question.status['categorized'] == '1'
            if categorized_expr not in exprs:
                exprs.append(categorized_expr)

            if similar_questions_to is not None:
                # questions need to be compared only when ontology matches exactly
                ques = cls.get(similar_questions_to)
                exprs.append(Question.ontology == ques.ontology)
                # exclude itself
                exprs.append(Question.id != ques.id)
                exprs.append(
                    Question.similar_question_ids.any(similar_questions_to))

            if not_similar_questions_to is not None:
                # questions need to be compared only when ontology matches exactly
                ques = cls.get(not_similar_questions_to)
                exprs.append(Question.ontology == ques.ontology)
                # exclude itself
                exprs.append(Question.id != ques.id)
                exprs.append(
                    or_(
                        not_(
                            Question.similar_question_ids.any(
                                not_similar_questions_to)),
                        Question.similar_question_ids == None))
        if limit is not None:
            questions_pag_obj = Question.query.filter(*exprs).order_by(
                Question.created_at.desc()).paginate(page, limit)
            questions = questions_pag_obj.items
            total = questions_pag_obj.total
        else:
            q = Question.query.filter(*exprs).order_by(
                Question.created_at.desc())
            questions = q.all()
            total = len(questions)

        question_dict = OrderedDict()
        for q in questions:
            question_dict[q.id] = q

        submissions = SolutionSubmission.query.filter(
            SolutionSubmission.question_id.in_(question_dict.keys())).all()
        for item in submissions:
            question = question_dict[item.question_id]
            if item.solution_type == 'text' and question.text_solution is not None:
                question.text_solution_by_type = item.submitted_by_type
                question.text_solution_by_id = item.submitted_by_id
            if item.solution_type == 'video' and question.video_solution_url is not None:
                question.video_solution_by_type = item.submitted_by_type
                question.video_solution_by_id = item.submitted_by_id

        return {'questions': question_dict.values(), 'total': total}

    @classmethod
    def reset_categorization(cls, id):
        """
        Reset the categorization flag and categorization fields

        :param id:
        :return:
        """
        question = cls.query.get(id)
        question.status['categorized'] = '0'
        question.status['proof_read_categorization'] = '0'
        question.status['finalized'] = '0'
        if question.ontology is not None:
            question.ontology = question.ontology[0:1]
        question.average_time = None
        question.nature = None
        question.difficulty = None
        question.type = None
        db.session.commit()
        return question

    @classmethod
    def approve_categorization(cls, id):
        """
        Approve the categorization. Set the categorization flag and proof read categorization flag

        :param id:
        :return:
        """
        question = cls.query.get(id)
        question.status['categorized'] = '1'
        question.status['proof_read_categorization'] = '1'
        if '0' not in (question.status['proof_read_categorization'],
                       question.status['proof_read_text_solution'],
                       question.status['proof_read_video_solution']):
            # question has been proof read in all 3 respects so its finalized
            question.status['finalized'] = '1'
        db.session.commit()
        return question

    @classmethod
    def reset_solution(cls, id, sol_type):
        """
        Reset the solution flags and solution field

        :param id:
        :param sol_type: Solution type. Can be text or video
        :return:
        """
        question = cls.query.get(id)
        if sol_type == 'text':
            question.status['text_solution_added'] = '0'
            question.status['proof_read_text_solution'] = '0'
            question.text_solution = None
            question.text_solution_by_type = None
            question.text_solution_by_id = None

        if sol_type == 'video':
            question.status['video_solution_added'] = '0'
            question.status['proof_read_video_solution'] = '0'
            question.video_solution_url = None
            question.video_solution_by_type = None
            question.video_solution_by_id = None

        question.status['finalized'] = '0'
        db.session.commit()
        return question

    @classmethod
    def approve_solution(cls, id, sol_type):
        """
        Approve the solution. Set the solution flag and proof read solution flag

        :param id:
        :param sol_type: Solution type. Can be text or video
        :return:
        """
        question = cls.query.get(id)
        if sol_type == 'text':
            question.status['text_solution_added'] = '1'
            question.status['proof_read_text_solution'] = '1'

        if sol_type == 'video':
            question.status['video_solution_added'] = '1'
            question.status['proof_read_video_solution'] = '1'

        if '0' not in (question.status['proof_read_categorization'],
                       question.status['proof_read_text_solution'],
                       question.status['proof_read_video_solution']):
            # question has been proof read in all 3 respects so its finalized
            question.status['finalized'] = '1'
        db.session.commit()
        return question
示例#5
0
class MockTest(db.Model):
    __tablename__ = 'mock_tests'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(app.config['TEST_NAME_MAX_LENGTH']))
    created_by_type = db.Column(db.Integer, db.ForeignKey('user_types.id'))
    created_by_id = db.Column(db.Integer)
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    question_ids = db.Column(JSON)
    target_exam = db.Column(
        db.Enum(*app.config['TARGET_EXAMS'].keys(), name='target_exams_enum'))
    difficulty = db.Column(
        db.Enum(*app.config['MOCK_TEST_DIFFICULTY_LEVEL'].keys(),
                name='mock_test_difficulty_enum'))
    description = db.Column(db.Text)
    for_institutes = db.Column(db.Boolean, default=False)
    is_locked = db.Column(db.Boolean, default=False)
    type = db.Column(
        db.Enum(*app.config['MOCK_TEST_TYPES'].keys(),
                name='mock_test_types_enum'))
    type_id = db.Column(db.Integer,
                        db.ForeignKey('ontology.id', ondelete='CASCADE'))
    prerequisite_id = db.Column(
        db.Integer, db.ForeignKey('mock_tests.id', ondelete='CASCADE'))
    duration = db.Column(db.Integer)
    syllabus = db.Column(db.Text)
    cutoff = db.Column(db.Float)
    date_closed = db.Column(
        db.Boolean,
        default=False)  # will the test open on a date or is it by default open
    opening_date = db.Column(db.Date, nullable=True)

    @classmethod
    def create(cls,
               name,
               difficulty,
               target_exam,
               for_institutes,
               question_ids,
               type,
               type_id,
               prerequisite_id,
               duration,
               created_by_type,
               created_by_id,
               date_closed,
               description=None,
               syllabus=None,
               cutoff=None,
               opening_date=None):
        mock_test = cls(name=name,
                        difficulty=difficulty,
                        target_exam=target_exam,
                        for_institutes=for_institutes,
                        question_ids=question_ids,
                        description=description,
                        syllabus=syllabus,
                        type=type,
                        type_id=type_id,
                        prerequisite_id=prerequisite_id,
                        duration=duration,
                        cutoff=cutoff,
                        created_by_type=created_by_type,
                        created_by_id=created_by_id,
                        date_closed=date_closed,
                        opening_date=opening_date)

        db.session.add(mock_test)
        db.session.commit()
        return mock_test
示例#6
0
class CategorySubmission(db.Model):
    __tablename__ = 'category_submissions'
    id = db.Column(db.Integer, primary_key=True)
    submitted_by_type = db.Column(db.Integer, db.ForeignKey('user_types.id'))
    submitted_by_id = db.Column(db.Integer)

    # JSON in the form of {"ontology": [1,10,13], "type": "", "nature": , ...}
    category = db.Column(JSON)

    question_id = db.Column(db.Integer,
                            db.ForeignKey('questions.id', ondelete='CASCADE'))
    submitted_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    status = db.Column(
        db.Enum(*app.config['SUBMISSION_STATUS'],
                name='submission_status_enum'))

    @classmethod
    def create(cls,
               submitted_by_type,
               submitted_by_id,
               question_id,
               ontology=None,
               nature=None,
               type=None,
               difficulty=None,
               average_time=None,
               status=None):
        """
        Create a new categorization submission

        :param submitted_by_type: the user type who submitted
        :param submitted_by_id: the user id who submitted
        :param question_id: the question id this submission corresponds too
        :param ontology: the ontology node's complete path
        :param type:
        :param difficulty:
        :param average_time:
        :param status:
        :return:
        """
        category = {
            'ontology': ontology,
            'type': type,
            'difficulty': difficulty,
            'average_time': average_time,
            'nature': nature
        }
        submission = cls(submitted_by_type=submitted_by_type,
                         submitted_by_id=submitted_by_id,
                         question_id=question_id,
                         category=json.dumps(category),
                         status=status)
        db.session.add(submission)
        db.session.commit()
        return submission

    @classmethod
    def get(cls,
            nature=None,
            type=None,
            difficulty=None,
            average_time=None,
            ontology=None,
            page=1,
            limit=10):
        """
        Get filtered category submissions

        :param nature:
        :param type:
        :param difficulty:
        :param average_time:
        :param ontology:
        :return:
        """

        data = Question.get_filtertered_list(nature=nature,
                                             type=type,
                                             difficulty=difficulty,
                                             average_time=average_time,
                                             ontology=ontology,
                                             categorized='1',
                                             proof_read_categorization='0',
                                             page=page,
                                             limit=limit)
        if data['total'] > 0:
            temp = OrderedDict()
            for q in data['questions']:
                temp[q.id] = {'question': q}

            submissions = cls.query.filter(cls.question_id.in_(temp.keys()),
                                           cls.status == None).all()
            for submission in submissions:
                temp[submission.question_id]['submission_id'] = submission.id
            return temp.values(), data['total']
        else:
            return [], 0
示例#7
0
class Ontology(db.Model):
    __tablename__ = 'ontology'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(app.config['NAME_MAX_LENGTH']))
    parent_path = db.Column(ARRAY(db.Integer))
    theory = db.Column(db.Text)
    target_exams = db.Column(ARRAY(db.String))
    type = db.Column(
        db.Enum(*app.config['ONTOLOGY_NODE_TYPES'].keys(),
                name='ontology_node_types_enum'))
    clazz = db.Column(
        db.Enum(*app.config['ONTOLOGY_NODE_CLASSES'].keys(),
                name='ontology_node_classes_enum'))
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)

    @classmethod
    def create_node(cls,
                    name,
                    theory=None,
                    parent_id=None,
                    target_exams=None,
                    type=None,
                    clazz=None):
        """
        Create a new node in the ontology tree. If no parent id is provided a root node is create

        :param name: name of the node
        :param theory: theory text of the node
        :param parent_id: id of parent node
        :param target_exams: exams the node belongs to. only applies to root nodes of ontology
        :param type: type of node
        :param clazz: class of a node
        :return: the newly created node or an exception if parent_id is invalid, or if target exam is invalid
        """
        parent_path = []
        if parent_id is not None:
            # non root node
            parent = cls.query.get(parent_id)
            if parent is None:
                raise InvalidOntologyNodeId
            else:
                parent_path = parent.parent_path
                parent_path.append(parent_id)
                # only root node can correspond to exams
                target_exams = None
        else:
            # root node
            if target_exams is None or len(target_exams) < 1:
                raise AtleastOneTargetExamNeededForOntologyRootNode
            else:
                if not set(target_exams).issubset(
                        app.config['TARGET_EXAMS'].keys()):
                    raise UnknownTargetExam

        if type is not None:
            if type not in app.config['ONTOLOGY_NODE_TYPES']:
                raise UnknownOntologyNodeType
        if clazz is not None:
            if type not in app.config['ONTOLOGY_NODE_TYPES']:
                raise UnknownOntologyNodeClass

        node = cls(name=name,
                   theory=theory,
                   parent_path=parent_path,
                   target_exams=target_exams,
                   type=type,
                   clazz=clazz)
        db.session.add(node)
        db.session.commit()
        return node

    @classmethod
    def is_leaf_node(cls, node_id):
        """
        Returns `True` on the truth of either of the two conditions:
        1. If the node_id provided is of leaf node or not
        2. If the node_id is of the topic node (This may or may not be a leaf node)

        :param node_id:
        :return: true/false or exception if node_id is not present
        """
        node = cls.query.get(node_id)
        if node is None:
            raise InvalidOntologyNodeId

        _is_topic_node = lambda node_obj: node_obj.type is '3'
        _is_leaf_node = lambda node_id: cls.query.filter(
            cls.parent_path.any(node_id)).first() is None

        if (_is_topic_node(node) or _is_leaf_node(node_id)):
            return True
        return False

    @classmethod
    def delete_leaf_node(cls, node_id):
        """
        Delete a leaf node. If node is a non leaf node, an exception is raised

        :param node_id:
        :return:
        """
        if cls.is_leaf_node(node_id):
            node = cls.query.get(node_id)
            db.session.delete(node)
            db.session.commit()
        else:
            raise CannotDeleteNonLeafOntologyNode

    @classmethod
    def update_node_theory(cls, node_id, theory):
        """
        Update theory text of a leaf node. If node is a non leaf node, an exception is raised

        :param node_id:
        :param theory:
        :return:
        """
        if cls.is_leaf_node(node_id):
            node = cls.query.get(node_id)
            node.theory = theory
            db.session.commit()
            return node
        else:
            raise CannotUpdateTheoryOfNonLeafOntologyNode

    @classmethod
    def get_all_children_of_node(cls, node_id, get_theory=True):
        """
        Returns a list of children nodes of a node

        :param node_id:
        :param get_theory: if True return theory also else provide a boolean `theory_exists`
        :return: a list
        """
        if get_theory:
            return cls.query.filter(cls.parent_path.any(node_id)).all()
        else:
            nodes = cls.query.filter(cls.parent_path.any(node_id)).all()
            for node in nodes:
                node.theory_exists = node.theory is not None
                del node.theory
            return nodes

    @classmethod
    def get_all_nodes_of_tree(cls, get_theory=True):
        """
        Returns a list of nodes of the tree
        :param get_theory: if True return theory also else provide a boolean `theory_exists`
        :return: a list
        """
        if get_theory:
            return cls.query.filter().all()
        else:
            nodes = cls.query.filter().all()
            for node in nodes:
                node.theory_exists = node.theory is not None
                del node.theory
            return nodes

    @property
    def absolute_path(self):
        """
        Returns the absolute path of an ontology node

        :return: a list of node_ids
        """
        path = self.parent_path[:]
        path.append(self.id)
        return path
示例#8
0
class Batch(db.Model):
    __tablename__ = 'batches'
    __table_args__ = (db.UniqueConstraint('name', 'institute_id'), )
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(app.config['NAME_MAX_LENGTH']))
    on_weekdays = db.Column(db.Boolean, default=False)
    on_weekends = db.Column(db.Boolean, default=False)
    clazz = db.Column(db.Enum('11', '12', name='classes_enum'))
    target_year = db.Column(db.Integer)
    type = db.Column(db.Enum(*app.config['BATCH_TYPE'].keys(), name='batch_types_enum'))
    target_exam = db.Column(db.Enum(*app.config['TARGET_EXAMS'].keys(), name='target_exams_enum'))
    other = db.Column(db.Text)
    batch_timings = db.Column(db.String(20))
    institute_id = db.Column(db.Integer, db.ForeignKey('institutes.id'))
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    status = db.Column(db.Integer, default=1)


    @classmethod
    def create(cls, name, on_weekdays, on_weekends, clazz, target_year, target_exam, type, other, batch_timings, institute_id):
        """
        Create a new batch
        :param name:
        :param on_weekdays:
        :param on_weekends:
        :param clazz:
        :param target_year:
        :param target_exam:
        :param type:
        :param other: some text about batch
        :param batch_timings: string in the form ``h1:m1-h2:m2``
        :param institute_id:
        :return:
        """
        batch = cls(name=name, on_weekdays=on_weekdays, on_weekends=on_weekends, clazz=clazz, target_year=target_year,
            target_exam=target_exam, type=type, other=other, batch_timings=batch_timings, institute_id=institute_id)
        db.session.add(batch)
        try:
            db.session.commit()
        except IntegrityError:
            db.session.rollback()
            raise BatchNameAlreadyTaken
        return batch

    @classmethod
    def get(cls, id):
        """
        Get a single batch

        :param id:
        :return:
        """

        batch = cls.query.get(id)
        if batch is None:
            raise InvalidBatchId
        return batch

    @classmethod
    def get_filtered(cls, days=None, type=None, target_year=None, target_exam=None, include_ids=None, institute_id=None, status=None, branches=None):
        """
        Get a list of batches after applying filters

        :param days:
        :param type:
        :param target_year:
        :param target_exam:
        :param include_ids:
        :param institute_id:
        :param status:
        :param target_exam_list:
        :return:
        """
        exprs = []
        if days is not None:
            if days == 'weekdays':
                exprs.append(Batch.on_weekdays == True)
            if days == 'weekends':
                exprs.append(Batch.on_weekends == True)
        if type is not None:
            exprs.append(Batch.type == type)
        if target_year is not None:
            exprs.append(Batch.target_year == target_year)
        if target_exam is not None:
            exprs.append(Batch.target_exam == target_exam)
        if institute_id is not None:
            exprs.append(Batch.institute_id == institute_id)
        if include_ids is not None and (isinstance(include_ids, list) or isinstance(include_ids, tuple) or isinstance(include_ids, set)):
            exprs.append(Batch.id.in_(list(include_ids)))
        if branches is not None and (isinstance(branches, list) or isinstance(branches, tuple) or isinstance(branches, set)):
            target_exam_list = []
            engineering_exams = ['1', '2', '3']
            medical_exams = ['4', '5']
            if '1' in branches:
                target_exam_list.extend(engineering_exams)
            if '2' in branches:
                target_exam_list.extend(medical_exams)
            exprs.append(Batch.target_exam.in_(list(target_exam_list)))

        if status is None:
            status = 1
        # explicitly ignoring status
        if status != -1:
            exprs.append(Batch.status == status)

        return Batch.query.filter(*exprs).order_by(Batch.created_at.desc()).all()
class SolutionSubmission(db.Model):
    __tablename__ = 'solution_submissions'
    id = db.Column(db.Integer, primary_key=True)
    submitted_by_type = db.Column(db.Integer, db.ForeignKey('user_types.id'))
    submitted_by_id = db.Column(db.Integer)
    solution_type = db.Column(
        db.Enum('text', 'video', name='solution_types_enum'))
    solution = db.Column(db.Text)
    question_id = db.Column(db.Integer,
                            db.ForeignKey('questions.id', ondelete='CASCADE'))
    submitted_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    status = db.Column(
        db.Enum(*app.config['SUBMISSION_STATUS'],
                name='submission_status_enum'))

    @classmethod
    def create(cls,
               submitted_by_type,
               submitted_by_id,
               question_id,
               solution_type,
               solution,
               status=None):
        """
        Create a new solution submission

        :param submitted_by_type: the user type who submitted
        :param submitted_by_id: the user id who submitted
        :param question_id: the question id this submission corresponds too
        :param solution_type: text/video
        :param solution:
        :param status:
        :return:
        """
        submission = cls(submitted_by_type=submitted_by_type,
                         submitted_by_id=submitted_by_id,
                         question_id=question_id,
                         solution_type=solution_type,
                         solution=solution,
                         status=status)
        db.session.add(submission)
        db.session.commit()
        return submission

    @classmethod
    def get(cls,
            nature=None,
            type=None,
            difficulty=None,
            average_time=None,
            ontology=None,
            sol_type='text',
            page=1,
            limit=10):
        """
        Get filtered category submissions

        :param nature:
        :param type:
        :param difficulty:
        :param average_time:
        :param ontology:
        :return:
        """
        if sol_type == 'text':
            data = Question.get_filtertered_list(nature=nature,
                                                 type=type,
                                                 difficulty=difficulty,
                                                 average_time=average_time,
                                                 ontology=ontology,
                                                 text_solution_added='1',
                                                 proof_read_text_solution='0',
                                                 page=page,
                                                 limit=limit)
        else:
            data = Question.get_filtertered_list(nature=nature,
                                                 type=type,
                                                 difficulty=difficulty,
                                                 average_time=average_time,
                                                 ontology=ontology,
                                                 video_solution_added='1',
                                                 proof_read_video_solution='0',
                                                 page=page,
                                                 limit=limit)
        if data['total'] > 0:
            temp = OrderedDict()
            for q in data['questions']:
                temp[q.id] = {'question': q}

            submissions = cls.query.filter(
                cls.question_id.in_(temp.keys()), cls.status == None,
                cls.solution_type == sol_type).all()
            for submission in submissions:
                temp[submission.question_id]['submission_id'] = submission.id
            return temp.values(), data['total']
        else:
            return [], 0