예제 #1
0
class OrderingExerciseQuestion(OrderingExerciseQuestionOrderingExerciseQuestionItemJsonSerializer, ExerciseQuestion):
    """A list of items that need to be ordered. May be horizontal or vertical"""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Propositions
    items = db.ListField(db.EmbeddedDocumentField(OrderingExerciseQuestionItem))

    def without_correct_answer(self):
        son = super(OrderingExerciseQuestion, self).without_correct_answer()
        shuffle(son['items'])
        return son

    def answer_with_data(self, data):
        return OrderingExerciseQuestionAnswer.init_with_data(data)

    def get_items_by_id(self, itemsId):
        result = []
        for itemId in itemsId:
            print(itemId)
            result.append(self.get_item_by_id(itemId))
        return result

    def get_item_by_id(self, itemId):
        for item in self.items:
            if str(item._id) == itemId:
                return item
        return None
예제 #2
0
class DropdownExerciseQuestion(DropdownExerciseQuestionJsonSerializer,
                               ExerciseQuestion):
    """question where blanks need to be filled with word chosen from a dropdown list."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## the whole text, where blanks are tagged with [%N%] where N if Nth blank of the text
    text = db.StringField()

    ## Propositions
    dropdowns = db.ListField(
        db.EmbeddedDocumentField(DropdownExerciseQuestionDropdown))

    def without_correct_answer(self):
        son = super(DropdownExerciseQuestion, self).without_correct_answer()
        for dropdown in son['dropdowns']:
            for proposition in dropdown['propositions']:
                proposition.pop('is_correct_answer', None)
        return son

    def answer_with_data(self, data):
        return DropdownExerciseQuestionAnswer.init_with_data(data)

    def get_propositions_by_id(self, propositionsId):
        result = []
        for dropdown in self.dropdowns:
            for proposition in dropdown.propositions:
                if proposition._id in propositionsId:
                    result.append(proposition)
        return result
예제 #3
0
class RightOrWrongExerciseQuestion(RightOrWrongExerciseQuestionJsonSerializer,
                                   ExerciseQuestion):
    """Question with a right or wrong answer"""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Propositions
    propositions = db.ListField(
        db.EmbeddedDocumentField(RightOrWrongExerciseQuestionProposition))

    def without_correct_answer(self):
        son = super(RightOrWrongExerciseQuestion,
                    self).without_correct_answer()
        for proposition in son['propositions']:
            proposition.pop('is_correct_answer', None)
        return son

    def answer_with_data(self, data):
        return RightOrWrongExerciseQuestionAnswer.init_with_data(data)

    def get_proposition_by_id(self, propositionId):
        result = None
        for proposition in self.propositions:
            if proposition._id == propositionId:
                result = proposition
        return result
예제 #4
0
class MultipleAnswerMCQExerciseQuestion(MultipleAnswerMCQExerciseQuestionJsonSerializer, ExerciseQuestion):
    """Multiple choice question with several possible answers."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Propositions
    propositions = db.ListField(db.EmbeddedDocumentField(MultipleAnswerMCQExerciseQuestionProposition))

    def without_correct_answer(self):
        son = super(MultipleAnswerMCQExerciseQuestion, self).without_correct_answer()
        for proposition in son['propositions']:
            proposition.pop('is_correct_answer', None)
        return son

    def answer_with_data(self, data):
        return MultipleAnswerMCQExerciseQuestionAnswer.init_with_data(data)

    def get_propositions_by_id(self, propositions_id):
        result = []
        for proposition in self.propositions:
            print(str(proposition._id))
            if proposition._id in propositions_id:
                result.append(proposition)
        return result
예제 #5
0
class DropdownExerciseQuestionAnswer(
        DropdownExerciseQuestionAnswerJsonSerializer, ExerciseQuestionAnswer):
    """Answers given for a dropdown question"""

    ## The list of chosen propositions, identified by their ObjectIds
    given_propositions = db.ListField(db.ObjectIdField())

    @classmethod
    def init_with_data(cls, data):
        obj = cls()
        obj.given_propositions = []
        import re
        for key in data:
            match = re.match(r"dropdown_(\w+)", key)
            if match:
                obj.given_propositions.append(ObjectId(data[key]))
        return obj

    def is_correct(self, question, parameters):
        propositions = question.get_propositions_by_id(self.given_propositions)
        all_question_propositions = []
        for dropdown in question.dropdowns:
            all_question_propositions.extend(dropdown.propositions)
        correct_propositions = filter(
            lambda proposition: proposition.is_correct_answer,
            all_question_propositions)
        return set(propositions) == set(correct_propositions)
예제 #6
0
class UniqueAnswerMCQExerciseQuestion(
        UniqueAnswerMCQExerciseQuestionJsonSerializer, ExerciseQuestion):
    """Multiple choice question with one possible answer only."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Propositions
    propositions = db.ListField(
        db.EmbeddedDocumentField(UniqueAnswerMCQExerciseQuestionProposition))

    def without_correct_answer(self):
        son = super(UniqueAnswerMCQExerciseQuestion,
                    self).without_correct_answer()
        for proposition in son['propositions']:
            proposition.pop('is_correct_answer', None)
        return son

    def answer_with_data(self, data):
        return UniqueAnswerMCQExerciseQuestionAnswer.init_with_data(data)

    def get_proposition_by_id(self, propositionId):
        result = None
        for proposition in self.propositions:
            if proposition._id == propositionId:
                result = proposition
        return result
예제 #7
0
class CategorizeExerciseQuestion(CategorizeExerciseQuestionJsonSerializer,
                                 ExerciseQuestion):
    """A list of items that need to be categorized."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## categories
    categories = db.ListField(
        db.EmbeddedDocumentField(CategorizeExerciseQuestionCategory))

    def without_correct_answer(self):
        son = super(CategorizeExerciseQuestion, self).without_correct_answer()
        all_items = []
        for category in son['categories']:
            all_items.extend(category.pop('items'))
        shuffle(all_items)
        son['items'] = all_items
        return son

    def answer_with_data(self, data):
        return CategorizeExerciseQuestionAnswer.init_with_data(data)

    def get_category_by_id(self, category_id):
        for category in self.categories:
            if category._id == category_id:
                return category

    def get_items_in_category_by_id(self, category_id, items_id):
        result = []
        category = self.get_category_by_id(category_id)
        for item in category.items:
            if item._id in items_id:
                result.append(item)
        return result
예제 #8
0
class UnresolvedReference(JsonSerializer, db.Document):

    document = db.GenericReferenceField()

    field_path = db.StringField()

    class_name = db.StringField()

    central_id = db.ObjectIdField()

    def resolve(self):
        print "Trying to resolve %s" % unicode(self)
        try:
            from MookAPI.helpers import get_service_for_class
            service = get_service_for_class(self.class_name)
            local_document = service.get(central_id=self.central_id)
            self.document.set_value_for_field_path(local_document,
                                                   self.field_path)
            self.document.clean()
            self.document.save(validate=False)  # FIXME MongoEngine bug
            self.delete()
            print "==> Success!"
            return True
        except Exception as e:
            print "==> Failed!"
            return False

    def __unicode__(self):
        try:
            return "Reference to %s document %s in document %s at field path %s" \
                   % (self.class_name, str(self.central_id), self.document, self.field_path)
        except:
            return "Reference to %s document %s in %s document at field path %s" \
                   % (self.class_name, str(self.central_id), self.document.__class__.__name__, self.field_path)
예제 #9
0
class OrderingExerciseQuestionItem(OrderingExerciseQuestionItemJsonSerializer, db.EmbeddedDocument):
    """Stores an item for the overall ordering question."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    text = db.StringField()
예제 #10
0
class CategorizeExerciseQuestionItem(CategorizeExerciseQuestionItem,
                                     db.EmbeddedDocument):
    """Stores an item that belongs to one category."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    text = db.StringField()
예제 #11
0
class DropdownExerciseQuestionDropdown(
        DropdownExerciseQuestionDropdownJsonSerializer, db.EmbeddedDocument):
    """Stores a list of propositions to a blank field in a text."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    propositions = db.ListField(
        db.EmbeddedDocumentField(DropdownExerciseQuestionProposition))
예제 #12
0
class MultipleAnswerMCQExerciseQuestionProposition(MultipleAnswerMCQExerciseQuestionPropositionJsonSerializer, db.EmbeddedDocument):
    """Stores a proposition to a multiple-answer MCQ."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    text = db.StringField()

    ## Is correct answer
    is_correct_answer = db.BooleanField(default=False)
예제 #13
0
class DropdownExerciseQuestionProposition(
        DropdownExerciseQuestionPropositionJsonSerializer,
        db.EmbeddedDocument):
    """Stores a proposition to a blank field."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    text = db.StringField()

    ## Is correct answer
    is_correct_answer = db.BooleanField(default=False)
예제 #14
0
class RightOrWrongExerciseQuestionProposition(
        RightOrWrongExerciseQuestionPropositionJsonSerializer,
        db.EmbeddedDocument):
    """Stores a proposition to a right or wrong question."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    text = db.StringField()

    ## Is correct answer
    is_correct_answer = db.BooleanField(default=False)
예제 #15
0
class CategorizeExerciseQuestionCategory(
        CategorizeExerciseQuestionCategoryJsonSerializer, db.EmbeddedDocument):
    """Stores a category for the categorize question."""

    ## Object Id
    _id = db.ObjectIdField(default=ObjectId)

    ## Text
    title = db.StringField()

    ## items that belong to this category
    items = db.ListField(
        db.EmbeddedDocumentField(CategorizeExerciseQuestionItem))
예제 #16
0
class ExerciseQuestion(ExerciseQuestionJsonSerializer, db.EmbeddedDocument):
    """
    Generic collection, every question type will inherit from this.
    Subclasses should override method "without_correct_answer" in order to define the version sent to clients.
    Subclasses of questions depending on presentation parameters should also override
    method "with_computed_correct_answer".
    """

    meta = {'allow_inheritance': True, 'abstract': True}

    @property
    def id(self):
        return self._id

    ## Object Id
    _id = db.ObjectIdField(default=bson.ObjectId)

    ## Question text
    question_heading = db.StringField()

    ## Question image
    question_image = db.ImageField()

    @property
    def question_image_url(self, _external=True):
        if not self.question_image:
            return None

        if not hasattr(self, '_instance'):
            return None

        return url_for("resources.get_exercise_question_image",
                       resource_id=str(self._instance._instance.id),
                       question_id=str(self._id),
                       filename=self.question_image.filename,
                       _external=_external)

    ## Answer feedback (explanation of the right answer)
    answer_feedback = db.StringField()

    def without_correct_answer(self):
        son = self.to_json()
        son.pop('answer_feedback', None)
        return son

    def with_computed_correct_answer(self, parameters):
        son = self.to_json()
        return son

    def answer_with_data(self, data):
        return ExerciseQuestionAnswer.init_with_data(data)
예제 #17
0
class CategorizeExerciseQuestionAnswer(
        CategorizeExerciseQuestionAnswerJsonSerializer,
        ExerciseQuestionAnswer):
    """categorized items given for this Categorize question."""

    ## The categories sent by the client, identified by their ObjectIds.
    given_categories = db.ListField(db.ObjectIdField())

    ## The categorized items, identified by their ObjectIds, in the requested categories.
    ## The first level order is the same as the given_categories
    given_categorized_items = db.ListField(db.ListField(db.ObjectIdField()))

    @classmethod
    def init_with_data(cls, data):
        obj = cls()
        obj.given_categories = []
        obj.given_categorized_items = []
        for category, items in data['categorized_items'].iteritems():
            obj.given_categories.append(ObjectId(category))
            categorized_items = []
            for given_item in items:
                categorized_items.append(ObjectId(given_item))
            obj.given_categorized_items.append(categorized_items)
        return obj

    def is_correct(self, question, parameters):
        answer_categories_items = self.given_categorized_items
        result = True
        for i in range(0, len(answer_categories_items)):
            category_items = question.get_items_in_category_by_id(
                self.given_categories[i], self.given_categorized_items[i])
            correct_category = question.get_category_by_id(
                self.given_categories[i])
            if set(category_items) != set(correct_category.items):
                result = False
                break
        return result
예제 #18
0
class OrderingExerciseQuestionAnswer(OrderingExerciseQuestionAnswerJsonSerializer, ExerciseQuestionAnswer):
    """Ordered items given for this ordering question."""

    ## The given propositions, identified by their ObjectIds
    given_ordered_items = db.ListField(db.ObjectIdField())

    @classmethod
    def init_with_data(cls, data):
        obj = cls()
        obj.given_ordered_items = data['ordered_items']
        return obj

    def is_correct(self, question, parameters):
        ordered_items = question.get_items_by_id(self.given_ordered_items)
        correct_ordered_items = question.items
        return ordered_items == correct_ordered_items
예제 #19
0
class MultipleAnswerMCQExerciseQuestionAnswer(MultipleAnswerMCQExerciseQuestionAnswerJsonSerializer, ExerciseQuestionAnswer):
    """Answers given to a multiple-answer MCQ."""

    ## The list of chosen propositions, identified by their ObjectIds
    given_propositions = db.ListField(db.ObjectIdField())

    @classmethod
    def init_with_data(cls, data):
        obj = cls()
        obj.given_propositions = []
        for proposition in data['propositions']:
            obj.given_propositions.append(ObjectId(proposition))
        return obj

    def is_correct(self, question, parameters):
        propositions = question.get_propositions_by_id(self.given_propositions)
        correct_propositions = filter(lambda proposition: proposition.is_correct_answer, question.propositions)
        return set(propositions) == set(correct_propositions)
예제 #20
0
class UniqueAnswerMCQExerciseQuestionAnswer(
        UniqueAnswerMCQExerciseQuestionAnswerJsonSerializer,
        ExerciseQuestionAnswer):
    """Answer given to a unique-answer MCQ."""

    ## The chosen propositions, identified by its ObjectId
    given_proposition = db.ObjectIdField()

    @classmethod
    def init_with_data(cls, data):
        obj = cls()
        obj.given_proposition = data['proposition']
        return obj

    def is_correct(self, question, parameters):
        proposition = question.get_proposition_by_id(
            ObjectId(self.given_proposition))
        if (proposition != None):
            return proposition.is_correct_answer
        return False
예제 #21
0
class SyncableDocument(SyncableDocumentJsonSerializer, db.Document):
    """
    .. _SyncableDocument:

    An abstract class for any document that needs to be synced between the central server and a local server.
    """

    meta = {'allow_inheritance': True, 'abstract': True}

    ## Last modification
    last_modification = db.DateTimeField()
    """
    The date of the last modification on the document.
    Used to determine whether the document has changed since the last synchronization of a local server.
    """

    ## Id of the document on the central server
    central_id = db.ObjectIdField()
    """The id of the document on the central server."""

    # A placeholder for unresolved references that need to be saved after the document is saved
    unresolved_references = []

    @property
    def url(self, _external=False):
        """
        The URL where a JSON representation of the document based on MongoCoderMixin_'s encode_mongo_ method can be found.

        .. warning::

            Subclasses of MongoCoderDocument should implement this method.
        """

        raise exceptions.NotImplementedError(
            "The single-object URL of this document class is not defined.")

    def top_level_syncable_document(self):
        """
        If a ``SyncableDocument`` has child documents, this function returns the top-level parent document.
        Defaults to ``self``.

        .. note::

            Override this method if the document is a child of a ``SyncableDocument``.
        """

        return self

    def save(self, *args, **kwargs):
        self.last_modification = datetime.datetime.now()

        rv = super(SyncableDocument, self).save(*args, **kwargs)

        for ref in self.unresolved_references:
            ref.document = self
            ref.save()

        return rv

    def delete(self, *args, **kwargs):
        reference = DeletedSyncableDocument()
        reference.document = self
        reference.top_level_document = self.top_level_syncable_document()
        reference.save()
        return super(SyncableDocument, self).delete(*args, **kwargs)

    def all_synced_documents(self, local_server=None):
        """
        Returns the list of references to atomic documents that should be looked at when syncing this document.
        Defaults to a one-element list containing a reference to self.

        .. note::

            Override this method if this document has children documents.
            Children documents who reference this document should be inserted AFTER self.
            Children documents referenced from this document should be inserted BEFORE self.
        """

        return [self]

    def items_to_update(self, local_server):
        """
        .. _items_to_update:

        Returns the list of references to atomic documents that have changed since the last synchronization.
        Defaults to a one-element list containing a reference to self.

        .. note::

            Override this method if this document has children documents.
        """

        items = []
        last_sync = local_server.last_sync

        for item in self.all_synced_documents(local_server=local_server):
            if last_sync is None or item.last_modification is None or last_sync < item.last_modification:
                items.append(item)

        return items

    def items_to_delete(self, local_server):
        """
        .. _items_to_delete:

        Returns the list of references to atomic documents that have been deleted since the last synchronization.
        This method will also automatically check for any deleted children documents (no need to override as long as ``top_level_document`` is overridden).
        """

        items = []
        last_sync = local_server.last_sync

        for obj in DeletedSyncableDocument.objects.no_dereference().filter(
                top_level_document=self.top_level_syncable_document()):
            if last_sync is None or obj.date is None or last_sync < obj.date:
                items.append(obj.document)

        return items

    def items_to_sync(self, local_server=None):
        """
        Returns a dictionary ``dict`` with two keys:

        * ``dict['update']`` contains the results of the items_to_update_ method;
        * ``dict['delete']`` contains the results of the items_to_delete_ method.

        .. todo::

            Remove items that are in the ``delete`` list from the ``update`` list.
        """

        items = {}
        items['update'] = self.items_to_update(local_server=local_server)
        items['delete'] = self.items_to_delete(local_server=local_server)
        ## We should do some cleanup at this point, in particular remove deletable items from 'update' list.
        return items

    def __unicode__(self):
        return "Document with class %s and id %s" % (self.__class__.__name__,
                                                     str(self.id))
예제 #22
0
class SyncTask(db.Document):
    """A document that describes a sync operation to perform on the local server."""

    ### PROPERTIES

    queue_position = db.SequenceField()
    """An auto-incrementing counter determining the order of the operations to perform."""

    type = db.StringField(choices=('update', 'delete'))
    """The operation to perform (``update`` or ``delete``)."""

    central_id = db.ObjectIdField()
    """The ``id`` of the ``SyncableDocument`` on the central server."""

    class_name = db.StringField()
    """The class of the ``SyncableDocument`` affected by this operation."""

    url = db.StringField()
    """The URL at which the information of the document can be downloaded. Null if ``type`` is ``delete``."""

    errors = db.ListField(db.StringField())
    """The list of the errors that occurred while trying to perform the operation."""
    def __unicode__(self):
        return "%s %s document with central id %s" % (
            self.type, self.class_name, self.central_id)

    _service = None

    @property
    def service(self):
        if not self._service:
            from MookAPI.helpers import get_service_for_class
            self._service = get_service_for_class(self.class_name)
        return self._service

    @property
    def model(self):
        return self.service.__model__

    def _fetch_update(self, connector, local_document):
        r = connector.get(self.url)

        if r.status_code == 200:
            son = json_util.loads(r.text)
            document = self.model.from_json(
                son['data'],
                from_central=True,
                overwrite_document=local_document,
                upload_path=connector.local_files_path)
            document.clean()
            document.save(
                validate=False
            )  # FIXME MongoEngine bug, hopefully fixed in next version
            return document

        else:
            raise Exception("Could not fetch info, got status code %d" %
                            r.status_code)

    def _update_local_server(self, connector, local_document):
        tracks_before = set(local_document.synced_tracks)
        users_before = set(local_document.synced_users)

        document = self._fetch_update(connector, local_document)

        if document:
            from . import sync_tasks_service

            tracks_after = set(document.synced_tracks)
            users_after = set(document.synced_users)

            for new_track in tracks_after - tracks_before:
                sync_tasks_service.fetch_tasks_whole_track(
                    new_track.id, connector)
            for new_user in users_after - users_before:
                sync_tasks_service.fetch_tasks_whole_user(
                    new_user.id, connector)
            for obsolete_track in tracks_before - tracks_after:
                sync_tasks_service.create_delete_task_existing_document(
                    obsolete_track)
            for obsolete_user in users_before - users_after:
                sync_tasks_service.create_delete_task_existing_document(
                    obsolete_user)

        return document

    def _depile_update(self, connector):
        try:
            local_document = self.service.get(central_id=self.central_id)
        except Exception as e:  # TODO Distinguish between not found and found >1 results
            local_document = None

        if local_document:
            from MookAPI.services import local_servers
            if local_servers._isinstance(local_document):
                return self._update_local_server(connector, local_document)

        return self._fetch_update(connector, local_document)

    def _depile_delete(self):
        try:
            local_document = self.service.get(central_id=self.central_id)
        except Exception as e:
            pass  # FIXME What should we do in that case?
        else:
            for document in reversed(local_document.all_synced_documents()):
                document.delete()

        return True

    def depile(self, connector=None):

        rv = False

        try:
            if self.type == 'update':
                rv = self._depile_update(connector=connector)
            elif self.type == 'delete':
                rv = self._depile_delete()
            else:
                self.errors.append("Unrecognized task type")
                self.save()
                return False
        except Exception as e:
            self.errors.append(str(e.message) or str(e.strerror))
            self.save()
            rv = False
        else:
            self.delete()

        return rv