示例#1
0
    def test_has_changed_descendant__forced(self):
        topic_tree_node = tree()
        query = Metadata(topic_tree_node)

        self.set_tree_changed(topic_tree_node, False)

        topic_tree_node.refresh_from_db()
        results = query.annotate(**{HAS_CHANGED_DESCENDANT: HasChanged()})
        serialized = RootNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('has_changed_descendant'),
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))
        self.assertFalse(
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))

        video_node = topic_tree_node.get_descendants().filter(kind=content_kinds.TOPIC).first()\
            .get_descendants().first()
        video_node.changed = True
        video_node.save()

        topic_tree_node.refresh_from_db()
        results = query.annotate(**{HAS_CHANGED_DESCENDANT: HasChanged()})
        serialized = RootNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('has_changed_descendant'),
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))
        self.assertTrue(
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))
示例#2
0
    def test_resource_count(self):
        topic_tree_node = tree()
        query = Metadata(topic_tree_node)

        results = query.annotate(**{RESOURCE_COUNT: ResourceCount()})
        serialized = ContentNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('resource_count'),
            results.get(serialized.get('id')).get(RESOURCE_COUNT))
        self.assertEqual(5,
                         results.get(serialized.get('id')).get(RESOURCE_COUNT))
示例#3
0
    def test_has_changed_descendant(self):
        topic_tree_node = tree()
        query = Metadata(topic_tree_node)

        results = query.annotate(**{HAS_CHANGED_DESCENDANT: HasChanged()})
        serialized = RootNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('has_changed_descendant'),
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))
        self.assertTrue(
            results.get(serialized.get('id')).get(HAS_CHANGED_DESCENDANT))
示例#4
0
    def test_max_sort_order(self):
        topic_tree_node = tree()
        query = Metadata(topic_tree_node)

        results = query.annotate(**{MAX_SORT_ORDER: SortOrderMax()})
        serialized = RootNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('max_sort_order'),
            results.get(serialized.get('id')).get(MAX_SORT_ORDER))
        self.assertEqual(2.0,
                         results.get(serialized.get('id')).get(MAX_SORT_ORDER))
示例#5
0
    def test_descendant_count(self):
        topic_tree_node = tree()
        query = Metadata(topic_tree_node)

        results = query.annotate(**{DESCENDANT_COUNT: DescendantCount()})
        serialized = ContentNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('total_count'),
            results.get(serialized.get('id')).get(DESCENDANT_COUNT))
        self.assertEqual(
            7,
            results.get(serialized.get('id')).get(DESCENDANT_COUNT))
示例#6
0
    def test_max_sort_order__alternate(self):
        topic_tree_node = tree().get_descendants().filter(
            kind=content_kinds.TOPIC).first()
        query = Metadata(topic_tree_node)

        results = query.annotate(**{MAX_SORT_ORDER: SortOrderMax()})
        serialized = RootNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('max_sort_order'),
            results.get(serialized.get('id')).get(MAX_SORT_ORDER))
        self.assertEqual(4.0,
                         results.get(serialized.get('id')).get(MAX_SORT_ORDER))
示例#7
0
    def test_resource_size__topic(self):
        topic_tree_node = tree()
        nested_topic = topic_tree_node.get_descendants().filter(
            kind=content_kinds.TOPIC).first()
        self.create_coach_node(nested_topic)

        query = Metadata(topic_tree_node)
        results = query.annotate(**{RESOURCE_SIZE: ResourceSize()})
        serialized = ContentNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('resource_size'),
            results.get(serialized.get('id')).get(RESOURCE_SIZE))
        self.assertEqual(0,
                         results.get(serialized.get('id')).get(RESOURCE_SIZE))
示例#8
0
    def test_assessment_count(self):
        tree()
        node = ContentNode.objects.get(
            node_id='00000000000000000000000000000005')
        query = Metadata(node)

        results = query.annotate(**{ASSESSMENT_COUNT: AssessmentCount()})
        serialized = ContentNodeSerializer(node).data

        self.assertEqual(
            serialized.get('metadata').get('resource_count'),
            results.get(serialized.get('id')).get(ASSESSMENT_COUNT))
        self.assertEqual(
            3,
            results.get(serialized.get('id')).get(ASSESSMENT_COUNT))
示例#9
0
    def test_coach_count(self):
        topic_tree_node = tree()
        nested_topic = topic_tree_node.get_descendants().filter(
            kind=content_kinds.TOPIC).first()
        self.create_coach_node(nested_topic)

        query = Metadata(topic_tree_node)

        results = query.annotate(**{COACH_COUNT: CoachCount()})
        serialized = ContentNodeSerializer(topic_tree_node).data

        self.assertEqual(
            serialized.get('metadata').get('coach_count'),
            results.get(serialized.get('id')).get(COACH_COUNT))
        self.assertEqual(1, results.get(serialized.get('id')).get(COACH_COUNT))
示例#10
0
class RootNodeSerializer(SimplifiedContentNodeSerializer,
                         ContentNodeFieldMixin):
    channel_name = serializers.SerializerMethodField('retrieve_channel_name')
    metadata_query = Metadata(total_count=DescendantCount(),
                              resource_count=ResourceCount(),
                              max_sort_order=SortOrderMax(),
                              resource_size=Value(0,
                                                  output_field=IntegerField()),
                              has_changed_descendant=HasChanged())

    def retrieve_metadata(self, node):
        data = self.metadata_query.get(node.pk)
        data.update(self.get_creators(node.get_descendants()))
        return data

    def retrieve_channel_name(self, node):
        channel = node.get_channel()
        return channel.name if channel else None

    class Meta:
        model = ContentNode
        fields = ('title', 'id', 'kind', 'children', 'metadata', 'published',
                  'publishing', 'node_id', 'channel_name', 'prerequisite',
                  'is_prerequisite_of', 'ancestors', 'tree_id',
                  'role_visibility')
示例#11
0
    def to_representation(self, data):
        if self.child:
            query = data

            if not isinstance(data, QuerySet) and isinstance(data, (list, tuple)):
                query = ContentNode.objects.filter(pk__in=[n.pk for n in data])

            # update metadata_query with queryset for all data such that it minimizes queries
            for attr_query in ('metadata_query', 'ancestor_query'):
                attr_query_val = getattr(self.child, attr_query, None)
                if attr_query_val:
                    setattr(self.child, attr_query, Metadata(query.all(), **attr_query_val.annotations))
        return super(CustomListSerializer, self).to_representation(data)
示例#12
0
class ContentNodeSerializer(SimplifiedContentNodeSerializer,
                            ContentNodeFieldMixin):
    ancestors = serializers.SerializerMethodField('get_node_ancestors')
    valid = serializers.SerializerMethodField('check_valid')
    associated_presets = serializers.SerializerMethodField(
        'retrieve_associated_presets')
    original_channel = serializers.SerializerMethodField(
        'retrieve_original_channel')
    thumbnail_src = serializers.SerializerMethodField('retrieve_thumbail_src')
    tags = TagSerializer(many=True, read_only=False)
    metadata_query = Metadata(
        total_count=DescendantCount(),
        resource_count=ResourceCount(include_self=True),
        assessment_count=AssessmentCount(include_self=True),
        max_sort_order=SortOrderMax(include_self=True),
        resource_size=ResourceSize(include_self=True),
        has_changed_descendant=HasChanged(include_self=True),
        coach_count=CoachCount(include_self=True),
    )

    def retrieve_associated_presets(self, node):
        return list(node.get_associated_presets())

    def check_valid(self, node):
        isoriginal = node.node_id == node.original_source_node_id
        if node.kind_id == content_kinds.TOPIC:
            return True
        elif isoriginal and not node.license:
            return False
        elif isoriginal and node.license.copyright_holder_required and not node.copyright_holder:
            return False
        elif isoriginal and node.license.is_custom and not node.license_description:
            return False
        elif node.kind_id == content_kinds.EXERCISE:
            for aitem in node.assessment_items.exclude(
                    type=exercises.PERSEUS_QUESTION):
                answers = json.loads(aitem.answers)
                correct_answers = filter(lambda a: a['correct'], answers)
                if aitem.question == "" or len(answers) == 0 or len(correct_answers) == 0 or \
                        any(filter(lambda a: a['answer'] == "", answers)) or \
                        (aitem.type == exercises.SINGLE_SELECTION and len(correct_answers) > 1) or \
                        any(filter(lambda h: h['hint'] == "", json.loads(aitem.hints))):
                    return False
            return True
        else:
            return node.files.filter(preset__supplementary=False).exists()

    def retrieve_metadata(self, node):
        data = self.metadata_query.get(node.pk)

        if node.kind_id == content_kinds.EXERCISE:
            data.update(resource_count=data.get('assessment_count'))

        if node.kind_id != content_kinds.TOPIC:
            return data

        if not node.parent_id:  # Add extra data to root node
            data.update(
                self.get_creators(node.get_descendants(include_self=True)))

        return data

    def retrieve_original_channel(self, node):
        channel_id = node.original_channel_id
        channel = channel_id and Channel.objects.get(pk=channel_id)

        return {
            "id": channel.pk,
            "name": channel.name,
            "thumbnail_url": channel.get_thumbnail(),
        } if (channel and not channel.deleted) else None

    class Meta:
        list_serializer_class = CustomListSerializer
        model = ContentNode
        fields = ('title', 'changed', 'id', 'description', 'sort_order',
                  'author', 'copyright_holder', 'license', 'language',
                  'license_description', 'assessment_items',
                  'slideshow_slides', 'files', 'ancestors', 'modified',
                  'original_channel', 'kind', 'parent', 'children',
                  'published', 'associated_presets', 'valid', 'metadata',
                  'original_source_node_id', 'tags', 'extra_fields',
                  'prerequisite', 'is_prerequisite_of', 'node_id', 'tree_id',
                  'publishing', 'freeze_authoring_data', 'role_visibility',
                  'provider', 'aggregator', 'thumbnail_src')
示例#13
0
class SimplifiedContentNodeSerializer(BulkSerializerMixin,
                                      serializers.ModelSerializer):
    id = serializers.CharField(required=False)
    children = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    prerequisite = serializers.PrimaryKeyRelatedField(
        many=True, queryset=ContentNode.objects.all())
    is_prerequisite_of = serializers.PrimaryKeyRelatedField(many=True,
                                                            read_only=True)
    metadata = serializers.SerializerMethodField('retrieve_metadata')
    ancestors = serializers.SerializerMethodField('get_node_ancestors')
    metadata_query = Metadata(
        total_count=DescendantCount(),
        resource_count=ResourceCount(),
        coach_count=CoachCount(),
    )
    ancestor_query = Metadata(ancestors=AncestorArrayAgg())

    def retrieve_metadata(self, node):
        return self.metadata_query.get(node.pk)

    def get_node_ancestors(self, node):
        return filter(lambda a: a,
                      self.ancestor_query.get(node.pk).get('ancestors', []))

    @staticmethod
    def setup_eager_loading(queryset):
        """ Perform necessary eager loading of data. """
        queryset = queryset.prefetch_related('children').prefetch_related(
            'files').prefetch_related('assessment_items')
        return queryset

    def to_internal_value(self, data):
        """
        In order to be able to handle passing tag_name in array,
        we need to overwrite this method to bypass run_validation for tags
        """
        if not isinstance(data, dict):
            message = self.error_messages['invalid'].format(
                datatype=type(data).__name__)
            raise ValidationError(
                {api_settings.NON_FIELD_ERRORS_KEY: [message]})

        ret = OrderedDict()
        errors = OrderedDict()
        fields = self._writable_fields

        for field in fields:
            validate_method = getattr(self, 'validate_' + field.field_name,
                                      None)
            primitive_value = field.get_value(data)
            try:
                if field.field_name != 'tags':
                    validated_value = field.run_validation(primitive_value)
                else:
                    validated_value = primitive_value

                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            except DjangoValidationError as exc:
                errors[field.field_name] = list(exc.messages)
            except SkipField:
                pass
            else:
                set_value(ret, field.source_attrs, validated_value)

        if errors:
            raise ValidationError(errors)

        return ret

    def create(self, validated_data):
        ModelClass = self.Meta.model
        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        try:
            instance = ModelClass.objects.create(**validated_data)
        except TypeError as exc:
            msg = ('Got a `TypeError` when calling `%s.objects.create()`. '
                   'This may be because you have a writable field on the '
                   'serializer class that is not a valid argument to '
                   '`%s.objects.create()`. You may need to make the field '
                   'read-only, or override the %s.create() method to handle '
                   'this correctly.\nOriginal exception text was: %s.' %
                   (ModelClass.__name__, ModelClass.__name__,
                    self.__class__.__name__, exc))
            raise TypeError(msg)

        # Save many-to-many relationships after the instance is created.
        if self.validated_data['tags']:
            tag_list = []
            for tag in self.validated_data['tags']:
                # tag_list.append(ContentTag.objects.get_or_create(tag_name=tag['tag_name'])[0])
                tag_list.append(
                    ContentTag.objects.get_or_create(tag_name=tag)[0])
            setattr(instance, 'tags', tag_list)
            many_to_many.pop('tags')

        if many_to_many:
            for field_name, value in many_to_many.items():
                setattr(instance, field_name, value)

        instance.save()

        return instance

    def update(self, instance, validated_data):
        """
        Since we are not doing anything crazy about the nested writable field(tags here),
        so just bypass the raise_errors_on_nested_writes().
        This may need to change in the future when we need to do crazy things on nested writable field.
        """
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

    class Meta:
        model = ContentNode
        fields = ('title', 'id', 'sort_order', 'kind', 'children', 'parent',
                  'metadata', 'content_id', 'prerequisite',
                  'is_prerequisite_of', 'ancestors', 'tree_id', 'language',
                  'role_visibility')
示例#14
0
    def test_multiple(self):
        topic_tree_node1 = tree()
        topic_tree_node2 = tree()

        topic_tree1_topics = topic_tree_node1.get_descendants().filter(
            kind=content_kinds.TOPIC)
        video_node = self.create_coach_node(topic_tree1_topics.first())
        exercise_node = ContentNode.objects.get(
            tree_id=topic_tree_node2.tree_id,
            node_id='00000000000000000000000000000005')

        self.set_tree_changed(topic_tree_node1, False)
        self.set_tree_changed(topic_tree_node2, False)
        topic_tree1_topics.last().delete()

        nodes = ContentNode.objects.filter(pk__in=[
            topic_tree_node1.pk,
            topic_tree_node2.pk,
            video_node.pk,
            exercise_node.pk,
        ])
        query = Metadata(
            nodes, **{
                DESCENDANT_COUNT: DescendantCount(),
                RESOURCE_COUNT: ResourceCount(),
                ASSESSMENT_COUNT: AssessmentCount(),
                RESOURCE_SIZE: ResourceSize(),
                COACH_COUNT: CoachCount(),
                HAS_CHANGED_DESCENDANT: HasChanged(include_self=True),
                MAX_SORT_ORDER: SortOrderMax(),
            })

        topic_tree1_results = query.get(topic_tree_node1.pk)
        topic_tree2_results = query.get(topic_tree_node2.pk)
        video_node_results = query.get(video_node.pk)
        exercise_node_results = query.get(exercise_node.pk)

        self.assertIsNotNone(topic_tree1_results)
        self.assertEqual(6, topic_tree1_results.get(DESCENDANT_COUNT))
        self.assertEqual(5, topic_tree1_results.get(RESOURCE_COUNT))
        self.assertEqual(0, topic_tree1_results.get(ASSESSMENT_COUNT))
        self.assertEqual(0, topic_tree1_results.get(RESOURCE_SIZE))
        self.assertEqual(1, topic_tree1_results.get(COACH_COUNT))
        self.assertTrue(topic_tree1_results.get(HAS_CHANGED_DESCENDANT))
        self.assertEqual(1, topic_tree1_results.get(MAX_SORT_ORDER))

        self.assertIsNotNone(topic_tree2_results)
        self.assertEqual(7, topic_tree2_results.get(DESCENDANT_COUNT))
        self.assertEqual(5, topic_tree2_results.get(RESOURCE_COUNT))
        self.assertEqual(0, topic_tree2_results.get(ASSESSMENT_COUNT))
        self.assertEqual(0, topic_tree2_results.get(RESOURCE_SIZE))
        self.assertEqual(0, topic_tree2_results.get(COACH_COUNT))
        self.assertFalse(topic_tree2_results.get(HAS_CHANGED_DESCENDANT))
        self.assertEqual(2, topic_tree2_results.get(MAX_SORT_ORDER))

        self.assertIsNotNone(video_node_results)
        self.assertEqual(1, video_node_results.get(DESCENDANT_COUNT))
        self.assertEqual(1, video_node_results.get(RESOURCE_COUNT))
        self.assertEqual(0, video_node_results.get(ASSESSMENT_COUNT))
        self.assertEqual(
            video_node.files.aggregate(size=Sum('file_size')).get('size'),
            video_node_results.get(RESOURCE_SIZE))
        self.assertEqual(1, video_node_results.get(COACH_COUNT))
        self.assertFalse(video_node_results.get(HAS_CHANGED_DESCENDANT))
        self.assertEqual(video_node.sort_order,
                         video_node_results.get(MAX_SORT_ORDER))

        self.assertIsNotNone(exercise_node_results)
        self.assertEqual(1, exercise_node_results.get(DESCENDANT_COUNT))
        self.assertEqual(1, exercise_node_results.get(RESOURCE_COUNT))
        self.assertEqual(3, exercise_node_results.get(ASSESSMENT_COUNT))
        self.assertEqual(0, exercise_node_results.get(RESOURCE_SIZE))
        self.assertEqual(0, exercise_node_results.get(COACH_COUNT))
        self.assertFalse(exercise_node_results.get(HAS_CHANGED_DESCENDANT))
        self.assertEqual(exercise_node.sort_order,
                         exercise_node_results.get(MAX_SORT_ORDER))