Exemplo n.º 1
0
class LearnerProgressNotification(models.Model):
    id = (models.AutoField(auto_created=True,
                           primary_key=True,
                           serialize=True,
                           verbose_name="ID"), )
    notification_object = models.CharField(
        max_length=200, choices=NotificationObjectType.choices(), blank=True)
    notification_event = models.CharField(
        max_length=200, choices=NotificationEventType.choices(), blank=True)
    user_id = UUIDField()
    classroom_id = UUIDField()  # This is a Classroom id
    assignment_collections = JSONField(null=True, default=[])
    contentnode_id = UUIDField(null=True)
    lesson_id = UUIDField(null=True)
    quiz_id = UUIDField(null=True)
    quiz_num_correct = models.IntegerField(null=True)
    quiz_num_answered = models.IntegerField(null=True)
    reason = models.CharField(max_length=200,
                              choices=HelpReason.choices(),
                              blank=True)
    timestamp = DateTimeTzField(default=local_now)

    def __str__(self):
        return "{object} - {event}".format(object=self.notification_object,
                                           event=self.notification_event)

    class Meta:
        app_label = "notifications"
Exemplo n.º 2
0
class ContentSummaryLog(BaseLogModel):
    """
    This model provides an aggregate summary of all recorded interactions a user has had with
    a content item over time.
    """

    # Morango syncing settings
    morango_model_name = "contentsummarylog"

    user = models.ForeignKey(FacilityUser)
    content_id = UUIDField(db_index=True)
    channel_id = UUIDField()
    start_timestamp = DateTimeTzField()
    end_timestamp = DateTimeTzField(blank=True, null=True)
    completion_timestamp = DateTimeTzField(blank=True, null=True)
    time_spent = models.FloatField(help_text="(in seconds)",
                                   default=0.0,
                                   validators=[MinValueValidator(0)])
    progress = models.FloatField(
        default=0, validators=[MinValueValidator(0),
                               MaxValueValidator(1.01)])
    kind = models.CharField(max_length=200)
    extra_fields = JSONField(default={}, blank=True)

    def calculate_source_id(self):
        return self.content_id

    def save(self, *args, **kwargs):
        if self.progress < 0 or self.progress > 1.01:
            raise ValidationError(
                "Content summary progress out of range (0-1)")

        super(ContentSummaryLog, self).save(*args, **kwargs)
Exemplo n.º 3
0
class ContentSessionLog(BaseLogModel):
    """
    This model provides a record of interactions with a content item within a single visit to that content page.
    """

    # Morango syncing settings
    morango_model_name = "contentsessionlog"

    user = models.ForeignKey(FacilityUser, blank=True, null=True)
    content_id = UUIDField(db_index=True)
    visitor_id = models.UUIDField(blank=True, null=True)
    channel_id = UUIDField()
    start_timestamp = DateTimeTzField()
    end_timestamp = DateTimeTzField(blank=True, null=True)
    time_spent = models.FloatField(help_text="(in seconds)",
                                   default=0.0,
                                   validators=[MinValueValidator(0)])
    progress = models.FloatField(default=0, validators=[MinValueValidator(0)])
    kind = models.CharField(max_length=200)
    extra_fields = JSONField(default={}, blank=True)

    def save(self, *args, **kwargs):
        if self.progress < 0:
            raise ValidationError("Progress out of range (<0)")

        super(ContentSessionLog, self).save(*args, **kwargs)
Exemplo n.º 4
0
class ContentNode(MPTTModel):
    """
    The primary object type in a content database. Defines the properties that are shared
    across all content types.

    It represents videos, exercises, audio, documents, and other 'content items' that
    exist as nodes in content channels.
    """

    id = UUIDField(primary_key=True)
    parent = TreeForeignKey("self",
                            null=True,
                            blank=True,
                            related_name="children",
                            db_index=True)
    license_name = models.CharField(max_length=50, null=True, blank=True)
    license_description = models.TextField(null=True, blank=True)
    has_prerequisite = models.ManyToManyField("self",
                                              related_name="prerequisite_for",
                                              symmetrical=False,
                                              blank=True)
    related = models.ManyToManyField("self", symmetrical=True, blank=True)
    tags = models.ManyToManyField("ContentTag",
                                  symmetrical=False,
                                  related_name="tagged_content",
                                  blank=True)
    title = models.CharField(max_length=200)
    coach_content = models.BooleanField(default=False)

    # the content_id is used for tracking a user's interaction with a piece of
    # content, in the face of possibly many copies of that content. When a user
    # interacts with a piece of content, all substantially similar pieces of
    # content should be marked as such as well. We track these "substantially
    # similar" types of content by having them have the same content_id.
    content_id = UUIDField(db_index=True)
    channel_id = UUIDField(db_index=True)

    description = models.TextField(blank=True, null=True)
    sort_order = models.FloatField(blank=True, null=True)
    license_owner = models.CharField(max_length=200, blank=True)
    author = models.CharField(max_length=200, blank=True)
    kind = models.CharField(max_length=200,
                            choices=content_kinds.choices,
                            blank=True)
    available = models.BooleanField(default=False)
    lang = models.ForeignKey("Language", blank=True, null=True)

    class Meta:
        abstract = True
Exemplo n.º 5
0
class AssessmentMetaData(models.Model):
    """
    A model to describe additional metadata that characterizes assessment behaviour in Kolibri.
    This model contains additional fields that are only revelant to content nodes that probe a
    user's state of knowledge and allow them to practice to Mastery.
    ContentNodes with this metadata may also be able to be used within quizzes and exams.
    """

    id = UUIDField(primary_key=True)
    contentnode = models.ForeignKey("ContentNode",
                                    related_name="assessmentmetadata")
    # A JSON blob containing a serialized list of ids for questions that the assessment can present.
    assessment_item_ids = JSONField(default=[])
    # Length of the above assessment_item_ids for a convenience lookup.
    number_of_assessments = models.IntegerField()
    # A JSON blob describing the mastery model that is used to set this assessment as mastered.
    mastery_model = JSONField(default={})
    # Should the questions listed in assessment_item_ids be presented in a random order?
    randomize = models.BooleanField(default=False)
    # Is this assessment compatible with being previewed and answer filled for display in coach reports
    # and use in summative and formative tests?
    is_manipulable = models.BooleanField(default=False)

    class Meta:
        abstract = True
Exemplo n.º 6
0
class DeviceAppKey(models.Model):
    """
    This class stores a key that is checked to make sure that a webview
    is making requests from a privileged device (i.e. from inside an
    app-wrapper webview)
    """

    key = UUIDField(default=uuid4)

    def save(self, *args, **kwargs):
        self.pk = 1
        super(DeviceAppKey, self).save(*args, **kwargs)

    @classmethod
    def update_app_key(cls):
        app_key, created = cls.objects.get_or_create()
        app_key.key = uuid4().hex
        app_key.save()
        cache.set(APP_KEY_CACHE_KEY, app_key.key, 5000)
        return app_key

    @classmethod
    def get_app_key(cls):
        key = cache.get(APP_KEY_CACHE_KEY)
        if key is None:
            try:
                app_key = cls.objects.get()
            except cls.DoesNotExist:
                app_key = cls.update_app_key()
            key = app_key.key
            cache.set(APP_KEY_CACHE_KEY, key, 5000)
        return key
Exemplo n.º 7
0
class NotificationsLog(models.Model):
    id = (models.AutoField(auto_created=True,
                           primary_key=True,
                           serialize=True,
                           verbose_name="ID"), )
    coach_id = UUIDField()
    timestamp = DateTimeTzField(default=local_now)

    def __str__(self):
        return self.coach_id

    class Meta:
        app_label = "notifications"
Exemplo n.º 8
0
class Bookmark(AbstractFacilityDataModel):

    content_id = UUIDField(blank=True, null=True)
    channel_id = UUIDField(blank=True, null=True)
    contentnode_id = UUIDField()
    user = models.ForeignKey(FacilityUser, blank=False)

    morango_model_name = "bookmark"

    permissions = IsOwn()

    def infer_dataset(self, *args, **kwargs):
        if self.user_id:
            return self.cached_related_dataset_lookup("user")
        elif self.dataset_id:
            # confirm that there exists a facility with that dataset_id
            try:
                return Facility.objects.get(
                    dataset_id=self.dataset_id).dataset_id
            except Facility.DoesNotExist:
                pass
        # if no user or matching facility, infer dataset from the default facility
        facility = Facility.get_default_facility()
        assert facility, "Before you can save bookmarks, you must have a facility"
        return facility.dataset_id

    def calculate_partition(self):
        return "{dataset_id}:user-rw:{user_id}".format(
            dataset_id=self.dataset_id, user_id=self.user.id)

    class Meta:
        # Ensures that we do not save duplicates, otherwise raises a
        # django.db.utils.IntegrityError
        unique_together = (
            "user",
            "contentnode_id",
        )
Exemplo n.º 9
0
class ChannelMetadata(models.Model):
    """
    Holds metadata about all existing content databases that exist locally.
    """

    id = UUIDField(primary_key=True)
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=400, blank=True)
    author = models.CharField(max_length=400, blank=True)
    version = models.IntegerField(default=0)
    thumbnail = models.TextField(blank=True)
    last_updated = DateTimeTzField(null=True)
    # Minimum version of Kolibri that this content database is compatible with
    min_schema_version = models.CharField(max_length=50)
    root = models.ForeignKey("ContentNode")

    class Meta:
        abstract = True
Exemplo n.º 10
0
class ExamAttemptLog(BaseAttemptLog):
    """
    This model provides a summary of a user's interactions with a question in an exam
    """

    morango_model_name = "examattemptlog"

    examlog = models.ForeignKey(
        ExamLog, related_name="attemptlogs", blank=False, null=False
    )
    # We have no session logs associated with ExamLogs, so we need to record the channel and content
    # ids here
    content_id = UUIDField()

    def infer_dataset(self, *args, **kwargs):
        return self.cached_related_dataset_lookup("examlog")

    def calculate_partition(self):
        return self.dataset_id
Exemplo n.º 11
0
class File(models.Model):
    """
    The second to bottom layer of the contentDB schema, defines the basic building brick for content.
    Things it can represent are, for example, mp4, avi, mov, html, css, jpeg, pdf, mp3...
    """

    id = UUIDField(primary_key=True)
    # The foreign key mapping happens here as many File objects can map onto a single local file
    local_file = models.ForeignKey("LocalFile", related_name="files")
    contentnode = models.ForeignKey("ContentNode", related_name="files")
    preset = models.CharField(max_length=150,
                              choices=format_presets.choices,
                              blank=True)
    lang = models.ForeignKey("Language", blank=True, null=True)
    supplementary = models.BooleanField(default=False)
    thumbnail = models.BooleanField(default=False)
    priority = models.IntegerField(blank=True, null=True, db_index=True)

    class Meta:
        abstract = True
Exemplo n.º 12
0
class SyncQueue(models.Model):
    """
    This class maintains the queue of the devices that try to sync
    with this server
    """

    id = UUIDField(primary_key=True, default=uuid4)
    user = models.ForeignKey(FacilityUser,
                             on_delete=models.CASCADE,
                             null=False)
    datetime = models.DateTimeField(auto_now_add=True)
    updated = models.FloatField(default=time.time)
    # polling interval is 5 seconds by default
    keep_alive = models.FloatField(default=5.0)

    @classmethod
    def clean_stale(cls, expire=180.0):
        """
        This method will delete all the devices from the queue
        with the expire time (in seconds) exhausted
        """
        staled_time = time.time() - expire
        cls.objects.filter(updated__lte=staled_time).delete()
Exemplo n.º 13
0
class ContentTag(models.Model):
    id = UUIDField(primary_key=True)
    tag_name = models.CharField(max_length=30, blank=True)

    class Meta:
        abstract = True
Exemplo n.º 14
0
            if offset > 0:
                in_clause_elements.append(" OR ")
            in_clause_elements.append("%s IN (" % lhs)
            params.extend(lhs_params)
            sqls_params = tuple()
            param_group = ("(" + ",".join(
                "'{}'".format(p)
                for p in rhs_params[offset:offset + max_in_list_size]) + ")")
            in_clause_elements.append(param_group)
            in_clause_elements.append(")")
            params.extend(sqls_params)
        in_clause_elements.append(")")
        return "".join(in_clause_elements), params


UUIDField.register_lookup(UUIDIn)
CharField.register_lookup(UUIDIn)
ForeignKey.register_lookup(UUIDIn)


class FilterByUUIDQuerysetMixin(object):
    """
    As a workaround to the SQLITE_MAX_VARIABLE_NUMBER, so we can avoid having to chunk our queries,
    we pass in the list of ids (after being validated) as an inline query statement.
    """
    def filter_by_uuids(self, ids, validate=True):
        id_field = self.model._meta.pk.attname
        return self._by_uuids(ids, validate, id_field, True)

    def exclude_by_uuids(self, ids, validate=True):
        id_field = self.model._meta.pk.attname