Esempio n. 1
0
class NodeContributorsSerializer(JSONAPISerializer):
    """ Separate from UserSerializer due to necessity to override almost every field as read only
    """
    non_anonymized_fields = ['bibliographic', 'permission']
    filterable_fields = frozenset(['id', 'bibliographic', 'permission'])

    id = IDField(source='_id', required=True)
    type = TypeField()

    bibliographic = ser.BooleanField(
        help_text=
        'Whether the user will be included in citations for this node or not.',
        default=True)
    permission = ser.ChoiceField(
        choices=osf_permissions.PERMISSIONS,
        required=False,
        allow_null=True,
        default=osf_permissions.reduce_permissions(
            osf_permissions.DEFAULT_CONTRIBUTOR_PERMISSIONS),
        help_text=
        'User permission level. Must be "read", "write", or "admin". Defaults to "write".'
    )

    links = LinksField({'self': 'get_absolute_url'})

    users = RelationshipField(related_view='users:user-detail',
                              related_view_kwargs={'user_id': '<pk>'},
                              always_embed=True)

    class Meta:
        type_ = 'contributors'

    def get_absolute_url(self, obj):
        node_id = self.context['request'].parser_context['kwargs']['node_id']
        return absolute_reverse('nodes:node-contributor-detail',
                                kwargs={
                                    'node_id': node_id,
                                    'user_id': obj._id
                                })
Esempio n. 2
0
class UserIdentitiesSerializer(JSONAPISerializer):
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    external_id = ser.CharField(read_only=True)
    status = ser.CharField(read_only=True)

    links = LinksField({
        'self': 'get_absolute_url',
    })

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'users:user-identities-detail',
            kwargs={
                'user_id': self.context['request'].parser_context['kwargs']['user_id'],
                'version': self.context['request'].parser_context['kwargs']['version'],
                'identity_id': obj['_id'],
            },
        )

    class Meta:
        type_ = 'external-identities'
Esempio n. 3
0
class NodeLinksSerializer(JSONAPISerializer):

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    target_node_id = ser.CharField(source='node._id', help_text='The ID of the node that this Node Link points to')

    # TODO: We don't show the title because the current user may not have access to this node. We may want to conditionally
    # include this field in the future.
    # title = ser.CharField(read_only=True, source='node.title', help_text='The title of the node that this Node Link '
    #                                                                      'points to')

    class Meta:
        type_ = 'node_links'

    links = LinksField({
        'html': 'get_absolute_url',
    })

    def get_absolute_url(self, obj):
        pointer_node = Node.load(obj.node._id)
        return pointer_node.absolute_url

    def create(self, validated_data):
        request = self.context['request']
        user = request.user
        auth = Auth(user)
        node = self.context['view'].get_node()
        pointer_node = Node.load(validated_data['node']['_id'])
        if not pointer_node:
            raise exceptions.NotFound('Node not found.')
        try:
            pointer = node.add_pointer(pointer_node, auth, save=True)
            return pointer
        except ValueError:
            raise exceptions.ValidationError('Node link to node {} already in list'.format(pointer_node._id))

    def update(self, instance, validated_data):
        pass
Esempio n. 4
0
class LicenseSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'name',
        'id',
    ])
    non_anonymized_fields = ['type']
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    name = ser.CharField(required=True, help_text='License name')
    text = ser.CharField(required=True, help_text='Full text of the license')
    url = ser.URLField(required=False, help_text='URL for the license')
    required_fields = ser.ListField(source='properties', read_only=True,
                                    help_text='Fields required for this license (provided to help front-end validators)')
    links = LinksField({'self': 'get_absolute_url'})

    class Meta:
        type_ = 'licenses'

    def get_absolute_url(self, obj):
        return absolute_reverse('licenses:license-detail', kwargs={
            'license_id': obj._id,
            'version': self.context['request'].parser_context['kwargs']['version']
        })
Esempio n. 5
0
class UserSettingsSerializer(JSONAPISerializer):
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    two_factor_enabled = ser.SerializerMethodField()
    subscribe_osf_general_email = ser.SerializerMethodField()
    subscribe_osf_help_email = ser.SerializerMethodField()

    def get_two_factor_enabled(self, obj):
        try:
            two_factor = TwoFactorUserSettings.objects.get(owner_id=obj.id)
            return not two_factor.deleted
        except TwoFactorUserSettings.DoesNotExist:
            return False

    def get_subscribe_osf_general_email(self, obj):
        return obj.mailchimp_mailing_lists.get(MAILCHIMP_GENERAL_LIST, False)

    def get_subscribe_osf_help_email(self, obj):
        return obj.osf_mailing_lists.get(OSF_HELP_LIST, False)

    links = LinksField({
        'self': 'get_absolute_url',
    })

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'users:user_settings',
            kwargs={
                'user_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    class Meta:
        type_ = 'user_settings'
Esempio n. 6
0
class AlertSerializer(JSONAPISerializer):
    filterable_fields = frozenset(['location', 'id'])

    id = IDField(source='_id')
    type = TypeField()
    location = ser.CharField(max_length=255)

    links = LinksField({'self': 'get_absolute_url'})

    def get_absolute_url(self, obj):
        return obj.absolute_api_v2_url

    class Meta:
        type_ = 'alerts'

    def create(self, validated_data):
        Alert = apps.get_model('osf.DismissedAlert')
        alert = Alert(**validated_data)
        try:
            alert.save()
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.messages[0])

        return alert
Esempio n. 7
0
class NodeSerializer(JSONAPISerializer):
    # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it
    # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this
    # instance
    filterable_fields = frozenset([
        'id', 'title', 'description', 'public', 'tags', 'category',
        'date_created', 'date_modified', 'root', 'parent', 'contributors',
        'preprint'
    ])

    non_anonymized_fields = [
        'id', 'title', 'description', 'category', 'date_created',
        'date_modified', 'registration', 'tags', 'public', 'license', 'links',
        'children', 'comments', 'contributors', 'files', 'node_links',
        'parent', 'root', 'logs', 'wikis'
    ]

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    category_choices = settings.NODE_CATEGORY_MAP.items()
    category_choices_string = ', '.join(
        ["'{}'".format(choice[0]) for choice in category_choices])

    title = ser.CharField(required=True)
    description = ser.CharField(required=False,
                                allow_blank=True,
                                allow_null=True)
    category = ser.ChoiceField(choices=category_choices,
                               help_text='Choices: ' + category_choices_string)
    date_created = ser.DateTimeField(read_only=True)
    date_modified = ser.DateTimeField(read_only=True)
    registration = ser.BooleanField(read_only=True, source='is_registration')
    preprint = ser.BooleanField(read_only=True, source='is_preprint')
    fork = ser.BooleanField(read_only=True, source='is_fork')
    collection = ser.BooleanField(read_only=True, source='is_collection')
    tags = JSONAPIListField(child=NodeTagField(), required=False)
    node_license = NodeLicenseSerializer(read_only=True, required=False)
    template_from = ser.CharField(
        required=False,
        allow_blank=False,
        allow_null=False,
        help_text=
        'Specify a node id for a node you would like to use as a template for the '
        'new node. Templating is like forking, except that you do not copy the '
        'files, only the project structure. Some information is changed on the top '
        'level project by submitting the appropriate fields in the request body, '
        'and some information will not change. By default, the description will '
        'be cleared and the project will be made private.')

    current_user_can_comment = ser.SerializerMethodField(
        help_text='Whether the current user is allowed to post comments')
    current_user_permissions = ser.SerializerMethodField(
        help_text='List of strings representing the permissions '
        'for the current user on this node.')

    # Public is only write-able by admins--see update method
    public = ser.BooleanField(
        source='is_public',
        required=False,
        help_text='Nodes that are made public will give read-only access '
        'to everyone. Private nodes require explicit read '
        'permission. Write and admin access are the same for '
        'public and private nodes. Administrators on a parent '
        'node have implicit read permissions for all child nodes')

    links = LinksField({'html': 'get_absolute_html_url'})
    # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins

    license = RelationshipField(
        related_view='licenses:license-detail',
        related_view_kwargs={'license_id': '<node_license.node_license._id>'},
    )

    children = RelationshipField(
        related_view='nodes:node-children',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_node_count'},
    )

    comments = RelationshipField(
        related_view='nodes:node-comments',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'unread': 'get_unread_comments_count'},
        filter={'target': '<pk>'})

    contributors = RelationshipField(
        related_view='nodes:node-contributors',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_contrib_count'},
    )

    files = RelationshipField(related_view='nodes:node-providers',
                              related_view_kwargs={'node_id': '<pk>'})

    wikis = RelationshipField(related_view='nodes:node-wikis',
                              related_view_kwargs={'node_id': '<pk>'})

    forked_from = RelationshipField(
        related_view=lambda n: 'registrations:registration-detail'
        if getattr(n, 'is_registration', False) else 'nodes:node-detail',
        related_view_kwargs={'node_id': '<forked_from_id>'})

    template_node = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<template_node._id>'})

    forks = RelationshipField(related_view='nodes:node-forks',
                              related_view_kwargs={'node_id': '<pk>'})

    node_links = RelationshipField(
        related_view='nodes:node-pointers',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_pointers_count'},
    )

    parent = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<parent_node._id>'},
        filter_key='parent_node')

    draft_registrations = HideIfRegistration(
        RelationshipField(related_view='nodes:node-draft-registrations',
                          related_view_kwargs={'node_id': '<pk>'}))

    registrations = HideIfRegistration(
        RelationshipField(related_view='nodes:node-registrations',
                          related_view_kwargs={'node_id': '<pk>'},
                          related_meta={'count': 'get_registration_count'}))

    affiliated_institutions = RelationshipField(
        related_view='nodes:node-institutions',
        related_view_kwargs={'node_id': '<pk>'},
        self_view='nodes:node-relationships-institutions',
        self_view_kwargs={'node_id': '<pk>'})

    root = RelationshipField(related_view='nodes:node-detail',
                             related_view_kwargs={'node_id': '<root._id>'})

    logs = RelationshipField(related_view='nodes:node-logs',
                             related_view_kwargs={'node_id': '<pk>'},
                             related_meta={'count': 'get_logs_count'})

    linked_nodes = RelationshipField(
        related_view='nodes:linked-nodes',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_node_links_count'},
        self_view='nodes:node-pointer-relationship',
        self_view_kwargs={'node_id': '<pk>'},
        self_meta={'count': 'get_node_links_count'})

    view_only_links = RelationshipField(
        related_view='nodes:node-view-only-links',
        related_view_kwargs={'node_id': '<pk>'},
    )

    def get_current_user_permissions(self, obj):
        user = self.context['request'].user
        if user.is_anonymous():
            return ['read']
        permissions = obj.get_permissions(user=user)
        if not permissions:
            permissions = ['read']
        return permissions

    def get_current_user_can_comment(self, obj):
        user = self.context['request'].user
        auth = Auth(user if not user.is_anonymous() else None)
        return obj.can_comment(auth)

    class Meta:
        type_ = 'nodes'

    def get_absolute_url(self, obj):
        return obj.get_absolute_url()

    # TODO: See if we can get the count filters into the filter rather than the serializer.

    def get_logs_count(self, obj):
        return len(obj.logs)

    def get_node_count(self, obj):
        auth = get_user_auth(self.context['request'])
        nodes = [
            node for node in obj.nodes
            if node.can_view(auth) and node.primary and not node.is_deleted
        ]
        return len(nodes)

    def get_contrib_count(self, obj):
        return len(obj.contributors)

    def get_registration_count(self, obj):
        auth = get_user_auth(self.context['request'])
        registrations = [
            node for node in obj.registrations_all if node.can_view(auth)
        ]
        return len(registrations)

    def get_pointers_count(self, obj):
        return len(obj.nodes_pointer)

    def get_node_links_count(self, obj):
        count = 0
        auth = get_user_auth(self.context['request'])
        for pointer in obj.nodes_pointer:
            if not pointer.node.is_deleted and not pointer.node.is_collection and pointer.node.can_view(
                    auth):
                count += 1
        return count

    def get_unread_comments_count(self, obj):
        user = get_user_auth(self.context['request']).user
        node_comments = Comment.find_n_unread(user=user, node=obj, page='node')

        return {'node': node_comments}

    def create(self, validated_data):
        request = self.context['request']
        user = request.user
        if 'template_from' in validated_data:
            template_from = validated_data.pop('template_from')
            template_node = Node.load(key=template_from)
            if template_node is None:
                raise exceptions.NotFound
            if not template_node.has_permission(
                    user, 'read', check_parent=False):
                raise exceptions.PermissionDenied

            validated_data.pop('creator')
            changed_data = {template_from: validated_data}
            node = template_node.use_as_template(auth=get_user_auth(request),
                                                 changes=changed_data)
        else:
            node = Node(**validated_data)
        try:
            node.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        if is_truthy(request.GET.get('inherit_contributors')
                     ) and validated_data['parent'].has_permission(
                         user, 'write'):
            auth = get_user_auth(request)
            parent = validated_data['parent']
            contributors = []
            for contributor in parent.contributors:
                if contributor is not user:
                    contributors.append({
                        'user':
                        contributor,
                        'permissions':
                        parent.get_permissions(contributor),
                        'visible':
                        parent.get_visible(contributor)
                    })
            node.add_contributors(contributors, auth=auth, log=True, save=True)
        return node

    def update(self, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node, Node), 'node must be a Node'
        auth = get_user_auth(self.context['request'])
        old_tags = set([tag._id for tag in node.tags])
        if 'tags' in validated_data:
            current_tags = set(validated_data.pop('tags', []))
        elif self.partial:
            current_tags = set(old_tags)
        else:
            current_tags = set()

        for new_tag in (current_tags - old_tags):
            node.add_tag(new_tag, auth=auth)
        for deleted_tag in (old_tags - current_tags):
            node.remove_tag(deleted_tag, auth=auth)

        if validated_data:
            try:
                node.update(validated_data, auth=auth)
            except ValidationValueError as e:
                raise InvalidModelValueError(detail=e.message)
            except PermissionsError:
                raise exceptions.PermissionDenied
            except NodeUpdateError as e:
                raise exceptions.ValidationError(detail=e.reason)
            except NodeStateError as e:
                raise InvalidModelValueError(detail=e.message)

        return node
Esempio n. 8
0
class BaseFileSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'name',
        'kind',
        'path',
        'materialized_path',
        'size',
        'provider',
        'last_touched',
        'tags',
    ])
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    guid = ser.SerializerMethodField(
        read_only=True,
        method_name='get_file_guid',
        help_text='OSF GUID for this file (if one has been assigned)',
    )
    checkout = CheckoutField()
    name = ser.CharField(
        read_only=True,
        help_text='Display name used in the general user interface')
    kind = ser.CharField(read_only=True, help_text='Either folder or file')
    path = ser.CharField(
        read_only=True,
        help_text='The unique path used to reference this object')
    size = ser.SerializerMethodField(
        read_only=True, help_text='The size of this file at this version')
    provider = ser.CharField(
        read_only=True,
        help_text='The Add-on service this file originates from')
    materialized_path = ser.CharField(
        read_only=True,
        help_text=
        'The Unix-style path of this object relative to the provider root',
    )
    last_touched = VersionedDateTimeField(
        read_only=True,
        help_text=
        'The last time this file had information fetched about it via the OSF')
    date_modified = ser.SerializerMethodField(
        read_only=True, help_text='Timestamp when the file was last modified')
    date_created = ser.SerializerMethodField(
        read_only=True, help_text='Timestamp when the file was created')
    extra = ser.SerializerMethodField(
        read_only=True, help_text='Additional metadata about this file')
    tags = JSONAPIListField(child=FileTagField(), required=False)
    current_user_can_comment = ser.SerializerMethodField(
        help_text='Whether the current user is allowed to post comments')
    current_version = ser.IntegerField(help_text='Latest file version',
                                       read_only=True,
                                       source='current_version_number')
    delete_allowed = ser.BooleanField(read_only=True, required=False)

    parent_folder = RelationshipField(
        related_view='files:file-detail',
        related_view_kwargs={'file_id': '<parent._id>'},
        help_text='The folder in which this file exists',
    )
    files = NodeFileHyperLinkField(
        related_view=lambda node: disambiguate_files_related_view(node),
        view_lambda_argument='target',
        related_view_kwargs=lambda filenode:
        disambiguate_files_related_view_kwargs(filenode),
        kind='folder',
    )
    versions = NodeFileHyperLinkField(
        related_view='files:file-versions',
        related_view_kwargs={'file_id': '<_id>'},
        kind='file',
    )
    comments = HideIfPreprint(
        FileRelationshipField(
            related_view='nodes:node-comments',
            related_view_kwargs={'node_id': '<target._id>'},
            related_meta={'unread': 'get_unread_comments_count'},
            filter={'target': 'get_file_guid'},
        ))
    metadata_records = FileRelationshipField(
        related_view='files:metadata-records',
        related_view_kwargs={'file_id': '<_id>'},
    )

    links = LinksField({
        'info':
        Link('files:file-detail', kwargs={'file_id': '<_id>'}),
        'move':
        WaterbutlerLink(),
        'upload':
        WaterbutlerLink(),
        'delete':
        WaterbutlerLink(),
        'download':
        'get_download_link',
        'render':
        'get_render_link',
        'html':
        'absolute_url',
        'new_folder':
        WaterbutlerLink(must_be_folder=True, kind='folder'),
    })

    def absolute_url(self, obj):
        if obj.is_file:
            return furl.furl(settings.DOMAIN).set(
                path=(obj.target._id, 'files', obj.provider,
                      obj.path.lstrip('/')), ).url

    def get_download_link(self, obj):
        if obj.is_file:
            return get_file_download_link(
                obj,
                view_only=self.context['request'].query_params.get(
                    'view_only'))

    def get_render_link(self, obj):
        if obj.is_file:
            mfr_url = get_mfr_url(obj.target, obj.provider)
            download_url = self.get_download_link(obj)
            return get_file_render_link(mfr_url, download_url)

    class Meta:
        type_ = 'files'

    def get_size(self, obj):
        if obj.versions.exists():
            self.size = obj.versions.first().size
            return self.size
        return None

    def get_date_modified(self, obj):
        mod_dt = None
        if obj.provider == 'osfstorage' and obj.versions.exists():
            # Each time an osfstorage file is added or uploaded, a new version object is created with its
            # date_created equal to the time of the update.  The external_modified is the modified date
            # from the backend the file is stored on.  This field refers to the modified date on osfstorage,
            # so prefer to use the created of the latest version.
            mod_dt = obj.versions.first().created
        elif obj.provider != 'osfstorage' and obj.history:
            mod_dt = obj.history[-1].get('modified', None)

        if self.context['request'].version >= '2.2' and obj.is_file and mod_dt:
            return datetime.strftime(mod_dt, '%Y-%m-%dT%H:%M:%S.%fZ')

        return mod_dt and mod_dt.replace(tzinfo=pytz.utc)

    def get_date_created(self, obj):
        creat_dt = None
        if obj.provider == 'osfstorage' and obj.versions.exists():
            creat_dt = obj.versions.last().created
        elif obj.provider != 'osfstorage' and obj.history:
            # Non-osfstorage files don't store a created date, so instead get the modified date of the
            # earliest entry in the file history.
            creat_dt = obj.history[0].get('modified', None)

        if self.context[
                'request'].version >= '2.2' and obj.is_file and creat_dt:
            return datetime.strftime(creat_dt, '%Y-%m-%dT%H:%M:%S.%fZ')

        return creat_dt and creat_dt.replace(tzinfo=pytz.utc)

    def get_extra(self, obj):
        metadata = {}
        if obj.provider == 'osfstorage' and obj.versions.exists():
            metadata = obj.versions.first().metadata
        elif obj.provider != 'osfstorage' and obj.history:
            metadata = obj.history[-1].get('extra', {})

        extras = {}
        extras['hashes'] = {  # mimic waterbutler response
            'md5': metadata.get('md5', None),
            'sha256': metadata.get('sha256', None),
        }
        if obj.provider == 'osfstorage' and obj.is_file:
            extras['downloads'] = obj.get_download_count()
        return extras

    def get_current_user_can_comment(self, obj):
        user = self.context['request'].user
        auth = Auth(user if not user.is_anonymous else None)
        if isinstance(obj.target, AbstractNode):
            return obj.target.can_comment(auth)
        return False

    def get_unread_comments_count(self, obj):
        user = self.context['request'].user
        if user.is_anonymous:
            return 0
        return Comment.find_n_unread(user=user,
                                     node=obj.target,
                                     page='files',
                                     root_id=obj.get_guid()._id)

    def user_id(self, obj):
        # NOTE: obj is the user here, the meta field for
        # Hyperlinks is weird
        if obj:
            return obj._id
        return None

    def update(self, instance, validated_data):
        assert isinstance(instance,
                          BaseFileNode), 'Instance must be a BaseFileNode'
        if instance.provider != 'osfstorage' and 'tags' in validated_data:
            raise Conflict(
                'File service provider {} does not support tags on the OSF.'.
                format(instance.provider))
        auth = get_user_auth(self.context['request'])
        old_tags = set(instance.tags.values_list('name', flat=True))
        if 'tags' in validated_data:
            current_tags = set(validated_data.pop('tags', []))
        else:
            current_tags = set(old_tags)

        for new_tag in (current_tags - old_tags):
            instance.add_tag(new_tag, auth=auth)
        for deleted_tag in (old_tags - current_tags):
            instance.remove_tag(deleted_tag, auth=auth)

        for attr, value in validated_data.items():
            if attr == 'checkout':
                user = self.context['request'].user
                instance.check_in_or_out(user, value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

    def is_valid(self, **kwargs):
        return super(BaseFileSerializer, self).is_valid(clean_html=False,
                                                        **kwargs)

    def get_file_guid(self, obj):
        if obj:
            guid = obj.get_guid()
            if guid:
                return guid._id
        return None

    def get_absolute_url(self, obj):
        return api_v2_url('files/{}/'.format(obj._id))
Esempio n. 9
0
class FileMetadataRecordSerializer(JSONAPISerializer):

    id = IDField(source='_id', required=True)
    type = TypeField()

    metadata = ser.DictField()

    file = RelationshipField(
        related_view='files:file-detail',
        related_view_kwargs={'file_id': '<file._id>'},
    )

    schema = RelationshipField(
        related_view='schemas:file-metadata-schema-detail',
        related_view_kwargs={'schema_id': '<schema._id>'},
    )

    links = LinksField({
        'download': 'get_download_link',
        'self': 'get_absolute_url',
    })

    def validate_metadata(self, value):
        schema = from_json(self.instance.serializer.osf_schema)
        try:
            jsonschema.validate(value, schema)
        except jsonschema.ValidationError as e:
            if e.relative_schema_path[0] == 'additionalProperties':
                error_message = e.message
            else:
                error_message = 'Your response of {} for the field {} was invalid.'.format(
                    e.instance,
                    e.absolute_path[0],
                )
            raise InvalidModelValueError(detail=error_message,
                                         meta={'metadata_schema': schema})
        return value

    def update(self, record, validated_data):
        if validated_data:
            user = self.context['request'].user
            proposed_metadata = validated_data.pop('metadata')
            record.update(proposed_metadata, user)
        return record

    def get_download_link(self, obj):
        return absolute_reverse(
            'files:metadata-record-download',
            kwargs={
                'file_id':
                obj.file._id,
                'record_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_absolute_url(self, obj):
        return obj.absolute_api_v2_url

    class Meta:
        @staticmethod
        def get_type(request):
            return get_kebab_snake_case_field(request.version,
                                              'metadata-records')
Esempio n. 10
0
class CollectionSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'title',
        'date_created',
        'date_modified',
    ])

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    title = ser.CharField(required=True)
    date_created = ser.DateTimeField(read_only=True)
    date_modified = ser.DateTimeField(read_only=True)
    bookmarks = ser.BooleanField(read_only=False,
                                 default=False,
                                 source='is_bookmark_collection')

    links = LinksField({})

    node_links = RelationshipField(
        related_view='collections:node-pointers',
        related_view_kwargs={'collection_id': '<pk>'},
        related_meta={'count': 'get_node_links_count'})

    # TODO: Add a self link to this when it's available
    linked_nodes = RelationshipField(
        related_view='collections:linked-nodes',
        related_view_kwargs={'collection_id': '<pk>'},
        related_meta={'count': 'get_node_links_count'},
        self_view='collections:collection-node-pointer-relationship',
        self_view_kwargs={'collection_id': '<pk>'})

    class Meta:
        type_ = 'collections'

    def get_absolute_url(self, obj):
        return absolute_reverse('collections:collection-detail',
                                kwargs={'collection_id': obj._id})

    def get_node_links_count(self, obj):
        count = 0
        auth = get_user_auth(self.context['request'])
        for pointer in obj.nodes_pointer:
            if not pointer.node.is_deleted and not pointer.node.is_collection and pointer.node.can_view(
                    auth):
                count += 1
        return count

    def create(self, validated_data):
        node = Node(**validated_data)
        node.is_collection = True
        node.category = ''
        try:
            node.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        except NodeStateError:
            raise ser.ValidationError(
                'Each user cannot have more than one Bookmark collection.')
        return node

    def update(self, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node, Node), 'collection must be a Node'
        auth = get_user_auth(self.context['request'])

        if validated_data:
            try:
                node.update(validated_data, auth=auth)
            except ValidationValueError as e:
                raise InvalidModelValueError(detail=e.message)
            except PermissionsError:
                raise exceptions.PermissionDenied

        return node
Esempio n. 11
0
class ApiOAuth2ApplicationSerializer(ApiOAuthApplicationBaseSerializer):
    """Serialize data about a registered OAuth2 application"""

    id = IDField(
        source='client_id',
        read_only=True,
        help_text='The client ID for this application (automatically generated)'
    )

    type = TypeField()

    name = ser.CharField(
        help_text='A short, descriptive name for this application',
        required=True,
    )

    description = ser.CharField(
        help_text=
        'An optional description displayed to all users of this application',
        required=False,
        allow_blank=True,
    )
    home_url = ser.CharField(
        help_text="The full URL to this application's homepage.",
        required=True,
        validators=[URLValidator()],
        label='Home URL',
    )

    callback_url = ser.CharField(
        help_text=
        'The callback URL for this application (refer to OAuth documentation)',
        required=True,
        validators=[URLValidator()],
        label='Callback URL',
    )

    owner = ser.CharField(
        help_text='The id of the user who owns this application',
        read_only=
        True,  # Don't let user register an application in someone else's name
        source='owner._id',
    )

    date_created = VersionedDateTimeField(
        source='created',
        help_text=
        'The date this application was generated (automatically filled in)',
        read_only=True,
    )

    def create(self, validated_data):
        instance = ApiOAuth2Application(**validated_data)
        instance.save()
        return instance

    def update(self, instance, validated_data):
        assert isinstance(
            instance,
            ApiOAuth2Application), 'instance must be an ApiOAuth2Application'
        for attr, value in validated_data.iteritems():
            setattr(instance, attr, value)
        instance.save()
        return instance
Esempio n. 12
0
class UserSerializer(JSONAPISerializer):
    filterable_fields = frozenset(
        ['full_name', 'given_name', 'middle_names', 'family_name', 'id'])
    non_anonymized_fields = ['type']
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    full_name = ser.CharField(
        source='fullname',
        required=True,
        label='Full name',
        help_text='Display name used in the general user interface')
    given_name = ser.CharField(required=False,
                               allow_blank=True,
                               help_text='For bibliographic citations')
    middle_names = ser.CharField(required=False,
                                 allow_blank=True,
                                 help_text='For bibliographic citations')
    family_name = ser.CharField(required=False,
                                allow_blank=True,
                                help_text='For bibliographic citations')
    suffix = ser.CharField(required=False,
                           allow_blank=True,
                           help_text='For bibliographic citations')
    date_registered = ser.DateTimeField(read_only=True)

    # Social Fields are broken out to get around DRF complex object bug and to make API updating more user friendly.
    gitHub = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.github',
                                   allow_blank=True,
                                   help_text='GitHub Handle'),
                     required=False,
                     source='social.github'))
    scholar = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.scholar',
                                   allow_blank=True,
                                   help_text='Google Scholar Account'),
                     required=False,
                     source='social.scholar'))
    personal_website = DevOnly(
        AllowMissing(ser.URLField(required=False,
                                  source='social.personal',
                                  allow_blank=True,
                                  help_text='Personal Website'),
                     required=False,
                     source='social.personal'))
    twitter = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.twitter',
                                   allow_blank=True,
                                   help_text='Twitter Handle'),
                     required=False,
                     source='social.twitter'))
    linkedIn = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.linkedIn',
                                   allow_blank=True,
                                   help_text='LinkedIn Account'),
                     required=False,
                     source='social.linkedIn'))
    impactStory = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.impactStory',
                                   allow_blank=True,
                                   help_text='ImpactStory Account'),
                     required=False,
                     source='social.impactStory'))
    orcid = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.orcid',
                                   allow_blank=True,
                                   help_text='ORCID'),
                     required=False,
                     source='social.orcid'))
    researcherId = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.researcherId',
                                   allow_blank=True,
                                   help_text='ResearcherId Account'),
                     required=False,
                     source='social.researcherId'))
    researchGate = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.researchGate',
                                   allow_blank=True,
                                   help_text='ResearchGate Account'),
                     required=False,
                     source='social.researchGate'))
    academiaInstitution = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.academiaInstitution',
                                   allow_blank=True,
                                   help_text='AcademiaInstitution Field'),
                     required=False,
                     source='social.academiaInstitution'))
    academiaProfileID = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.academiaProfileID',
                                   allow_blank=True,
                                   help_text='AcademiaProfileID Field'),
                     required=False,
                     source='social.academiaProfileID'))
    baiduScholar = DevOnly(
        AllowMissing(ser.CharField(required=False,
                                   source='social.baiduScholar',
                                   allow_blank=True,
                                   help_text='Baidu Scholar Account'),
                     required=False,
                     source='social.baiduScholar'))
    links = LinksField({
        'html': 'absolute_url',
        'profile_image': 'profile_image_url',
    })

    nodes = RelationshipField(
        related_view='users:user-nodes',
        related_view_kwargs={'user_id': '<pk>'},
    )

    institutions = RelationshipField(
        related_view='users:user-institutions',
        related_view_kwargs={'user_id': '<pk>'},
        self_view='users:user-institutions-relationship',
        self_view_kwargs={'user_id': '<pk>'},
    )

    class Meta:
        type_ = 'users'

    def absolute_url(self, obj):
        if obj is not None:
            return obj.absolute_url
        return None

    def get_absolute_url(self, obj):
        return absolute_reverse('users:user-detail',
                                kwargs={'user_id': obj._id})

    def profile_image_url(self, user):
        size = self.context['request'].query_params.get('profile_image_size')
        return user.profile_image_url(size=size)

    def update(self, instance, validated_data):
        assert isinstance(instance, User), 'instance must be a User'
        for attr, value in validated_data.items():
            if 'social' == attr:
                for key, val in value.items():
                    instance.social[key] = val
            else:
                setattr(instance, attr, value)
        try:
            instance.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        return instance
Esempio n. 13
0
class UserSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'full_name', 'given_name', 'middle_names', 'family_name', 'id', 'uid'
    ])
    non_anonymized_fields = ['type']
    id = IDField(source='_id', read_only=True)
    uid = IDField(source='id', read_only=True)
    type = TypeField()
    full_name = ser.CharField(
        source='fullname',
        required=True,
        label='Full name',
        help_text='Display name used in the general user interface',
        max_length=186)
    given_name = ser.CharField(required=False,
                               allow_blank=True,
                               help_text='For bibliographic citations')
    middle_names = ser.CharField(required=False,
                                 allow_blank=True,
                                 help_text='For bibliographic citations')
    family_name = ser.CharField(required=False,
                                allow_blank=True,
                                help_text='For bibliographic citations')
    suffix = HideIfDisabled(
        ser.CharField(required=False,
                      allow_blank=True,
                      help_text='For bibliographic citations'))
    date_registered = HideIfDisabled(VersionedDateTimeField(read_only=True))
    active = HideIfDisabled(
        ser.BooleanField(read_only=True, source='is_active'))
    timezone = HideIfDisabled(
        ser.CharField(required=False,
                      help_text="User's timezone, e.g. 'Etc/UTC"))
    locale = HideIfDisabled(
        ser.CharField(required=False,
                      help_text="User's locale, e.g.  'en_US'"))
    social = ListDictField(required=False)
    can_view_reviews = ShowIfCurrentUser(
        ser.SerializerMethodField(
            help_text=
            'Whether the current user has the `view_submissions` permission to ANY reviews provider.'
        ))

    links = HideIfDisabled(
        LinksField({
            'html': 'absolute_url',
            'profile_image': 'profile_image_url',
        }))

    nodes = HideIfDisabled(
        RelationshipField(
            related_view='users:user-nodes',
            related_view_kwargs={'user_id': '<_id>'},
            related_meta={'projects_in_common': 'get_projects_in_common'},
        ))

    quickfiles = HideIfDisabled(
        QuickFilesRelationshipField(
            related_view='users:user-quickfiles',
            related_view_kwargs={'user_id': '<_id>'},
        ))

    registrations = HideIfDisabled(
        RelationshipField(
            related_view='users:user-registrations',
            related_view_kwargs={'user_id': '<_id>'},
        ))

    institutions = HideIfDisabled(
        RelationshipField(
            related_view='users:user-institutions',
            related_view_kwargs={'user_id': '<_id>'},
            self_view='users:user-institutions-relationship',
            self_view_kwargs={'user_id': '<_id>'},
        ))

    preprints = HideIfDisabled(
        RelationshipField(
            related_view='users:user-preprints',
            related_view_kwargs={'user_id': '<_id>'},
        ))

    class Meta:
        type_ = 'users'

    def get_projects_in_common(self, obj):
        user = get_user_auth(self.context['request']).user
        if obj == user:
            return user.contributor_to.count()
        return obj.n_projects_in_common(user)

    def absolute_url(self, obj):
        if obj is not None:
            return obj.absolute_url
        return None

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'users:user-detail',
            kwargs={
                'user_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

    def get_can_view_reviews(self, obj):
        group_qs = GroupObjectPermission.objects.filter(
            group__user=obj, permission__codename='view_submissions')
        return group_qs.exists() or obj.userobjectpermission_set.filter(
            permission__codename='view_submissions')

    def profile_image_url(self, user):
        size = self.context['request'].query_params.get('profile_image_size')
        return user.profile_image_url(size=size)

    def update(self, instance, validated_data):
        assert isinstance(instance, OSFUser), 'instance must be a User'
        for attr, value in validated_data.items():
            if 'social' == attr:
                for key, val in value.items():
                    # currently only profileWebsites are a list, the rest of the social key only has one value
                    if key == 'profileWebsites':
                        instance.social[key] = val
                    else:
                        if len(val) > 1:
                            raise InvalidModelValueError(
                                detail=
                                '{} only accept a list of one single value'.
                                format(key))
                        instance.social[key] = val[0]
            else:
                setattr(instance, attr, value)
        try:
            instance.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        except ValidationError as e:
            raise InvalidModelValueError(e)

        return instance
Esempio n. 14
0
class UserSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'full_name',
        'given_name',
        'middle_names',
        'family_name',
        'id',
    ])
    writeable_method_fields = frozenset([
        'accepted_terms_of_service',
    ])
    non_anonymized_fields = ['type']
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    full_name = ser.CharField(
        source='fullname',
        required=True,
        label='Full name',
        help_text='Display name used in the general user interface',
        max_length=186)
    given_name = ser.CharField(required=False,
                               allow_blank=True,
                               help_text='For bibliographic citations')
    middle_names = ser.CharField(required=False,
                                 allow_blank=True,
                                 help_text='For bibliographic citations')
    family_name = ser.CharField(required=False,
                                allow_blank=True,
                                help_text='For bibliographic citations')
    suffix = HideIfDisabled(
        ser.CharField(required=False,
                      allow_blank=True,
                      help_text='For bibliographic citations'))
    date_registered = HideIfDisabled(VersionedDateTimeField(read_only=True))
    active = HideIfDisabled(
        ser.BooleanField(read_only=True, source='is_active'))
    timezone = HideIfDisabled(
        ser.CharField(required=False,
                      help_text="User's timezone, e.g. 'Etc/UTC"))
    locale = HideIfDisabled(
        ser.CharField(required=False,
                      help_text="User's locale, e.g.  'en_US'"))
    social = SocialField(required=False, min_version='2.10')
    employment = JSONAPIListField(required=False, source='jobs')
    education = JSONAPIListField(required=False, source='schools')
    can_view_reviews = ShowIfCurrentUser(
        ser.SerializerMethodField(
            help_text=
            'Whether the current user has the `view_submissions` permission to ANY reviews provider.'
        ))
    accepted_terms_of_service = ShowIfCurrentUser(ser.SerializerMethodField())

    links = HideIfDisabled(
        LinksField(
            {
                'html': 'absolute_url',
                'profile_image': 'profile_image_url',
            }, ))

    nodes = HideIfDisabled(
        RelationshipField(
            related_view='users:user-nodes',
            related_view_kwargs={'user_id': '<_id>'},
            related_meta={
                'projects_in_common': 'get_projects_in_common',
                'count': 'get_node_count',
            },
        ))

    quickfiles = HideIfDisabled(
        QuickFilesRelationshipField(
            related_view='users:user-quickfiles',
            related_view_kwargs={'user_id': '<_id>'},
            related_meta={'count': 'get_quickfiles_count'},
        ))

    registrations = HideIfDisabled(
        RelationshipField(
            related_view='users:user-registrations',
            related_view_kwargs={'user_id': '<_id>'},
            related_meta={'count': 'get_registration_count'},
        ))

    institutions = HideIfDisabled(
        RelationshipField(
            related_view='users:user-institutions',
            related_view_kwargs={'user_id': '<_id>'},
            self_view='users:user-institutions-relationship',
            self_view_kwargs={'user_id': '<_id>'},
            related_meta={'count': 'get_institutions_count'},
        ))

    preprints = HideIfDisabled(
        RelationshipField(
            related_view='users:user-preprints',
            related_view_kwargs={'user_id': '<_id>'},
            related_meta={'count': 'get_preprint_count'},
        ))

    emails = ShowIfCurrentUser(
        RelationshipField(
            related_view='users:user-emails',
            related_view_kwargs={'user_id': '<_id>'},
        ))

    default_region = ShowIfCurrentUser(
        RegionRelationshipField(
            related_view='regions:region-detail',
            related_view_kwargs={'region_id': 'get_default_region_id'},
            read_only=False,
        ))

    settings = ShowIfCurrentUser(
        RelationshipField(
            related_view='users:user_settings',
            related_view_kwargs={'user_id': '<_id>'},
            read_only=True,
        ))

    class Meta:
        type_ = 'users'

    def get_projects_in_common(self, obj):
        user = get_user_auth(self.context['request']).user
        if obj == user:
            return user.contributor_to.count()
        return obj.n_projects_in_common(user)

    def absolute_url(self, obj):
        if obj is not None:
            return obj.absolute_url
        return None

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'users:user-detail',
            kwargs={
                'user_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_node_count(self, obj):
        auth = get_user_auth(self.context['request'])
        if obj != auth.user:
            return default_node_list_permission_queryset(
                user=auth.user,
                model_cls=Node).filter(contributor__user__id=obj.id).count()

        return default_node_list_queryset(model_cls=Node).filter(
            contributor__user__id=obj.id).count()

    def get_quickfiles_count(self, obj):
        return QuickFilesNode.objects.get(
            contributor__user__id=obj.id).files.filter(
                type='osf.osfstoragefile').count()

    def get_registration_count(self, obj):
        auth = get_user_auth(self.context['request'])
        user_registration = default_node_list_queryset(
            model_cls=Registration).filter(contributor__user__id=obj.id)
        return user_registration.can_view(
            user=auth.user, private_link=auth.private_link).count()

    def get_preprint_count(self, obj):
        auth_user = get_user_auth(self.context['request']).user
        user_preprints_query = Preprint.objects.filter(
            _contributors__guids___id=obj._id).exclude(machine_state='initial')
        return Preprint.objects.can_view(user_preprints_query,
                                         auth_user,
                                         allow_contribs=False).count()

    def get_institutions_count(self, obj):
        return obj.affiliated_institutions.count()

    def get_can_view_reviews(self, obj):
        group_qs = AbstractProviderGroupObjectPermission.objects.filter(
            group__user=obj, permission__codename='view_submissions')
        return group_qs.exists(
        ) or obj.abstractprovideruserobjectpermission_set.filter(
            permission__codename='view_submissions')

    def get_default_region_id(self, obj):
        try:
            # use the annotated value if possible
            region_id = obj.default_region
        except AttributeError:
            # use computed property if region annotation does not exist
            region_id = obj.osfstorage_region._id
        return region_id

    def get_accepted_terms_of_service(self, obj):
        return bool(obj.accepted_terms_of_service)

    def profile_image_url(self, user):
        size = self.context['request'].query_params.get('profile_image_size')
        return user.profile_image_url(size=size)

    def validate_employment(self, value):
        validate_user_json(value, 'employment-schema.json')
        return value

    def validate_education(self, value):
        validate_user_json(value, 'education-schema.json')
        return value

    def validate_social(self, value):
        schema = from_json('social-schema.json')
        try:
            jsonschema.validate(value, schema)
        except jsonschema.ValidationError as e:
            raise InvalidModelValueError(e)

        return value

    def update(self, instance, validated_data):
        assert isinstance(instance, OSFUser), 'instance must be a User'
        for attr, value in validated_data.items():
            if 'social' == attr:
                for key, val in value.items():
                    instance.social[key] = val
            elif 'accepted_terms_of_service' == attr:
                if value and not instance.accepted_terms_of_service:
                    instance.accepted_terms_of_service = timezone.now()
            elif 'region_id' == attr:
                region_id = validated_data.get('region_id')
                user_settings = instance._settings_model(
                    'osfstorage').objects.get(owner=instance)
                user_settings.default_region_id = region_id
                user_settings.save()
                instance.default_region = self.context['request'].data[
                    'default_region']
            else:
                setattr(instance, attr, value)
        try:
            instance.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        except ValidationError as e:
            raise InvalidModelValueError(e)

        return instance
Esempio n. 15
0
class CollectionSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'title',
        'date_created',
        'date_modified',
    ])

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    title = ser.CharField(required=True)
    date_created = ser.DateTimeField(read_only=True)
    date_modified = ser.DateTimeField(read_only=True)

    links = LinksField({})

    node_links = DevOnly(RelationshipField(
        related_view='collections:node-pointers',
        related_view_kwargs={'collection_id': '<pk>'},
        related_meta={'count': 'get_node_links_count'}
    ))

    # TODO: Add a self link to this when it's available
    linked_nodes = DevOnly(RelationshipField(
        related_view='collections:linked-nodes',
        related_view_kwargs={'collection_id': '<pk>'},
        related_meta={'count': 'get_node_links_count'}
    ))

    class Meta:
        type_ = 'collections'

    def get_absolute_url(self, obj):
        return obj.absolute_url

    def get_node_links_count(self, obj):
        return len(obj.nodes_pointer)

    def create(self, validated_data):
        node = Node(**validated_data)
        node.is_folder = True
        node.category = ''
        try:
            node.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        return node

    def update(self, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node, Node), 'collection must be a Node'
        auth = get_user_auth(self.context['request'])

        if validated_data:
            try:
                node.update(validated_data, auth=auth)
            except ValidationValueError as e:
                raise InvalidModelValueError(detail=e.message)
            except PermissionsError:
                raise exceptions.PermissionDenied

        return node
Esempio n. 16
0
class GuidSerializer(JSONAPISerializer):
    class Meta:
        type_ = 'guids'

    filterable_fields = tuple()

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    referent = RelationshipField(
        related_view=get_related_view,
        related_view_kwargs=get_related_view_kwargs,
        related_meta={
            'type': 'get_type',
        },
    )
    links = LinksField({
        'self': 'get_absolute_url',
        'html': 'get_absolute_html_url',
    })

    def get_type(self, guid):
        return get_type(guid.referent)

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'guids:guid-detail',
            kwargs={
                'guids':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_absolute_html_url(self, obj):
        if not isinstance(obj.referent, BaseFileNode):
            return obj.referent.absolute_url
        return urljoin(website_settings.DOMAIN, '/{}/'.format(obj._id))

    def to_representation(self, obj):
        if self.context['view'].kwargs.get('is_embedded'):
            # Force the referent to serialize instead.
            obj = obj.referent
            if isinstance(obj, Collection):
                view_kwargs = {'collection_id': obj._id}
            else:
                view_kwargs = {'node_id': obj._id}

            ser = resolve(
                reverse(
                    get_related_view(obj),
                    kwargs={
                        'version':
                        self.context['view'].kwargs.get('version', '2'),
                        **view_kwargs,
                    },
                ), ).func.cls.serializer_class(context=self.context, )

            [ser.context.update({k: v}) for k, v in self.context.items()]

            return ser.to_representation(obj)
        return super().to_representation(obj)
Esempio n. 17
0
class UserSettingsSerializer(JSONAPISerializer):
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    two_factor_enabled = ser.SerializerMethodField()
    two_factor_confirmed = ser.SerializerMethodField(read_only=True)
    subscribe_osf_general_email = ser.SerializerMethodField()
    subscribe_osf_help_email = ser.SerializerMethodField()
    deactivation_requested = ser.BooleanField(source='requested_deactivation',
                                              required=False)
    secret = ser.SerializerMethodField(read_only=True)

    def to_representation(self, instance):
        self.context['twofactor_addon'] = instance.get_addon('twofactor')
        return super(UserSettingsSerializer, self).to_representation(instance)

    def get_two_factor_enabled(self, obj):
        try:
            two_factor = TwoFactorUserSettings.objects.get(owner_id=obj.id)
            return not two_factor.deleted
        except TwoFactorUserSettings.DoesNotExist:
            return False

    def get_two_factor_confirmed(self, obj):
        two_factor_addon = self.context['twofactor_addon']
        if two_factor_addon and two_factor_addon.is_confirmed:
            return True
        return False

    def get_secret(self, obj):
        two_factor_addon = self.context['twofactor_addon']
        if two_factor_addon and not two_factor_addon.is_confirmed:
            return two_factor_addon.totp_secret_b32

    def get_subscribe_osf_general_email(self, obj):
        return obj.mailchimp_mailing_lists.get(MAILCHIMP_GENERAL_LIST, False)

    def get_subscribe_osf_help_email(self, obj):
        return obj.osf_mailing_lists.get(OSF_HELP_LIST, False)

    links = LinksField({
        'self': 'get_absolute_url',
        'export': 'get_export_link',
    })

    def get_export_link(self, obj):
        return absolute_reverse(
            'users:user-account-export',
            kwargs={
                'user_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'users:user_settings',
            kwargs={
                'user_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    class Meta:
        type_ = 'user_settings'
Esempio n. 18
0
class ApiOAuth2PersonalTokenSerializer(JSONAPISerializer):
    """Serialize data about a registered personal access token"""

    id = IDField(source='_id', read_only=True, help_text='The object ID for this token (automatically generated)')
    type = TypeField()

    name = ser.CharField(
        help_text='A short, descriptive name for this token',
        required=True,
    )

    owner = ser.CharField(
        help_text='The user who owns this token',
        read_only=True,  # Don't let user register a token in someone else's name
        source='owner._id',
    )

    scopes = ser.CharField(
        help_text='Governs permissions associated with this token',
        required=True,
    )

    token_id = ser.CharField(read_only=True, allow_blank=True)

    class Meta:
        type_ = 'tokens'

    links = LinksField({
        'html': 'absolute_url',
    })

    def absolute_url(self, obj):
        return obj.absolute_url

    def get_absolute_url(self, obj):
        return obj.absolute_api_v2_url

    def to_representation(self, obj, envelope='data'):
        data = super(ApiOAuth2PersonalTokenSerializer, self).to_representation(obj, envelope=envelope)
        # Make sure users only see token_id on create
        if not self.context['request'].method == 'POST':
            if 'data' in data:
                data['data']['attributes'].pop('token_id')
            else:
                data['attributes'].pop('token_id')

        return data

    def create(self, validated_data):
        validate_requested_scopes(validated_data)
        instance = ApiOAuth2PersonalToken(**validated_data)
        instance.save()
        return instance

    def update(self, instance, validated_data):
        validate_requested_scopes(validated_data)
        assert isinstance(instance, ApiOAuth2PersonalToken), 'instance must be an ApiOAuth2PersonalToken'

        instance.deactivate(save=False)  # This will cause CAS to revoke the existing token but still allow it to be used in the future, new scopes will be updated properly at that time.
        instance.reload()

        for attr, value in validated_data.iteritems():
            if attr == 'token_id':  # Do not allow user to update token_id
                continue
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance
Esempio n. 19
0
class FileSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'name',
        'node',
        'kind',
        'path',
        'materialized_path',
        'size',
        'provider',
        'last_touched',
        'tags',
    ])
    id = IDField(source='_id', read_only=True)
    type = TypeField()
    checkout = CheckoutField()
    name = ser.CharField(
        read_only=True,
        help_text='Display name used in the general user interface')
    kind = ser.CharField(read_only=True, help_text='Either folder or file')
    path = ser.CharField(
        read_only=True,
        help_text='The unique path used to reference this object')
    size = ser.SerializerMethodField(
        read_only=True, help_text='The size of this file at this version')
    provider = ser.CharField(
        read_only=True,
        help_text='The Add-on service this file originates from')
    materialized_path = ser.CharField(
        read_only=True,
        help_text=
        'The Unix-style path of this object relative to the provider root')
    last_touched = ser.DateTimeField(
        read_only=True,
        help_text=
        'The last time this file had information fetched about it via the OSF')
    date_modified = ser.SerializerMethodField(
        read_only=True, help_text='Timestamp when the file was last modified')
    date_created = ser.SerializerMethodField(
        read_only=True, help_text='Timestamp when the file was created')
    extra = ser.SerializerMethodField(
        read_only=True, help_text='Additional metadata about this file')
    tags = JSONAPIListField(child=FileTagField(), required=False)

    files = NodeFileHyperLinkField(related_view='nodes:node-files',
                                   related_view_kwargs={
                                       'node_id': '<node_id>',
                                       'path': '<path>',
                                       'provider': '<provider>'
                                   },
                                   kind='folder')
    versions = NodeFileHyperLinkField(related_view='files:file-versions',
                                      related_view_kwargs={'file_id': '<_id>'},
                                      kind='file')
    comments = FileCommentRelationshipField(
        related_view='nodes:node-comments',
        related_view_kwargs={'node_id': '<node._id>'},
        related_meta={'unread': 'get_unread_comments_count'},
        filter={'target': 'get_file_guid'})
    links = LinksField({
        'info':
        Link('files:file-detail', kwargs={'file_id': '<_id>'}),
        'move':
        WaterbutlerLink(),
        'upload':
        WaterbutlerLink(),
        'delete':
        WaterbutlerLink(),
        'download':
        WaterbutlerLink(must_be_file=True),
        'new_folder':
        WaterbutlerLink(must_be_folder=True, kind='folder'),
    })

    class Meta:
        type_ = 'files'

    def get_size(self, obj):
        if obj.versions:
            return obj.versions[-1].size
        return None

    def get_date_modified(self, obj):
        mod_dt = None
        if obj.provider == 'osfstorage' and obj.versions:
            # Each time an osfstorage file is added or uploaded, a new version object is created with its
            # date_created equal to the time of the update.  The date_modified is the modified date
            # from the backend the file is stored on.  This field refers to the modified date on osfstorage,
            # so prefer to use the date_created of the latest version.
            mod_dt = obj.versions[-1].date_created
        elif obj.provider != 'osfstorage' and obj.history:
            mod_dt = obj.history[-1].get('modified', None)

        return mod_dt and mod_dt.replace(tzinfo=pytz.utc)

    def get_date_created(self, obj):
        creat_dt = None
        if obj.provider == 'osfstorage' and obj.versions:
            creat_dt = obj.versions[0].date_created
        elif obj.provider != 'osfstorage' and obj.history:
            # Non-osfstorage files don't store a created date, so instead get the modified date of the
            # earliest entry in the file history.
            creat_dt = obj.history[0].get('modified', None)

        return creat_dt and creat_dt.replace(tzinfo=pytz.utc)

    def get_extra(self, obj):
        metadata = {}
        if obj.provider == 'osfstorage' and obj.versions:
            metadata = obj.versions[-1].metadata
        elif obj.provider != 'osfstorage' and obj.history:
            metadata = obj.history[-1].get('extra', {})

        extras = {}
        extras['hashes'] = {  # mimic waterbutler response
            'md5': metadata.get('md5', None),
            'sha256': metadata.get('sha256', None),
        }
        return extras

    def get_unread_comments_count(self, obj):
        user = self.context['request'].user
        if user.is_anonymous():
            return 0
        return Comment.find_n_unread(user=user,
                                     node=obj.node,
                                     page='files',
                                     root_id=obj.get_guid()._id)

    def user_id(self, obj):
        # NOTE: obj is the user here, the meta field for
        # Hyperlinks is weird
        if obj:
            return obj._id
        return None

    def update(self, instance, validated_data):
        assert isinstance(instance, FileNode), 'Instance must be a FileNode'
        if instance.provider != 'osfstorage' and 'tags' in validated_data:
            raise Conflict(
                'File service provider {} does not support tags on the OSF.'.
                format(instance.provider))
        auth = get_user_auth(self.context['request'])
        old_tags = set([tag._id for tag in instance.tags])
        if 'tags' in validated_data:
            current_tags = set(validated_data.pop('tags', []))
        else:
            current_tags = set(old_tags)

        for new_tag in (current_tags - old_tags):
            instance.add_tag(new_tag, auth=auth)
        for deleted_tag in (old_tags - current_tags):
            instance.remove_tag(deleted_tag, auth=auth)

        for attr, value in validated_data.items():
            if attr == 'checkout':
                user = self.context['request'].user
                instance.check_in_or_out(user, value)
            else:
                setattr(instance, attr, value)
        instance.save()
        return instance

    def is_valid(self, **kwargs):
        return super(FileSerializer, self).is_valid(clean_html=False, **kwargs)

    def get_file_guid(self, obj):
        if obj:
            guid = obj.get_guid()
            if guid:
                return guid._id
        return None

    def get_absolute_url(self, obj):
        return api_v2_url('files/{}/'.format(obj._id))
Esempio n. 20
0
class PreprintSerializer(TaxonomizableSerializerMixin, MetricsSerializerMixin,
                         JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'date_created',
        'date_modified',
        'date_published',
        'original_publication_date',
        'provider',
        'is_published',
        'subjects',
        'reviews_state',
        'node_is_public',
    ])
    available_metrics = frozenset([
        'downloads',
        'views',
    ])

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    date_created = VersionedDateTimeField(source='created', read_only=True)
    date_modified = VersionedDateTimeField(source='modified', read_only=True)
    date_published = VersionedDateTimeField(read_only=True)
    original_publication_date = VersionedDateTimeField(required=False,
                                                       allow_null=True)
    doi = ser.CharField(source='article_doi', required=False, allow_null=True)
    title = ser.CharField(required=True, max_length=512)
    description = ser.CharField(required=False,
                                allow_blank=True,
                                allow_null=True)
    is_published = NoneIfWithdrawal(ser.BooleanField(required=False))
    is_preprint_orphan = NoneIfWithdrawal(ser.BooleanField(read_only=True))
    license_record = NodeLicenseSerializer(required=False, source='license')
    tags = JSONAPIListField(child=NodeTagField(), required=False)
    node_is_public = ser.BooleanField(
        read_only=True,
        source='node__is_public',
        help_text='Is supplementary project public?')
    preprint_doi_created = NoneIfWithdrawal(
        VersionedDateTimeField(read_only=True))
    date_withdrawn = VersionedDateTimeField(read_only=True, allow_null=True)
    withdrawal_justification = HideIfNotWithdrawal(
        ser.CharField(required=False, read_only=True, allow_blank=True))
    current_user_permissions = ser.SerializerMethodField(
        help_text='List of strings representing the permissions '
        'for the current user on this preprint.', )
    public = ser.BooleanField(source='is_public',
                              required=False,
                              read_only=True)
    contributors = RelationshipField(
        related_view='preprints:preprint-contributors',
        related_view_kwargs={'preprint_id': '<_id>'},
    )
    reviews_state = ser.CharField(source='machine_state',
                                  read_only=True,
                                  max_length=15)
    date_last_transitioned = NoneIfWithdrawal(
        VersionedDateTimeField(read_only=True))

    citation = NoneIfWithdrawal(
        RelationshipField(
            related_view='preprints:preprint-citation',
            related_view_kwargs={'preprint_id': '<_id>'},
        ))

    identifiers = NoneIfWithdrawal(
        RelationshipField(
            related_view='preprints:identifier-list',
            related_view_kwargs={'preprint_id': '<_id>'},
        ))

    node = NoneIfWithdrawal(
        NodeRelationshipField(
            related_view='nodes:node-detail',
            related_view_kwargs={'node_id': '<node._id>'},
            read_only=False,
            many=False,
            self_view='preprints:preprint-node-relationship',
            self_view_kwargs={'preprint_id': '<_id>'},
        ))

    license = PreprintLicenseRelationshipField(
        related_view='licenses:license-detail',
        related_view_kwargs={'license_id': '<license.node_license._id>'},
        read_only=False,
    )

    provider = PreprintProviderRelationshipField(
        related_view='providers:preprint-providers:preprint-provider-detail',
        related_view_kwargs={'provider_id': '<provider._id>'},
        read_only=False,
    )

    files = NoneIfWithdrawal(
        RelationshipField(
            related_view='preprints:preprint-storage-providers',
            related_view_kwargs={'preprint_id': '<_id>'},
        ))

    primary_file = NoneIfWithdrawal(
        PrimaryFileRelationshipField(
            related_view='files:file-detail',
            related_view_kwargs={'file_id': '<primary_file._id>'},
            read_only=False,
        ))

    review_actions = RelationshipField(
        related_view='preprints:preprint-review-action-list',
        related_view_kwargs={'preprint_id': '<_id>'},
    )

    requests = NoneIfWithdrawal(
        RelationshipField(
            related_view='preprints:preprint-request-list',
            related_view_kwargs={'preprint_id': '<_id>'},
        ))

    links = LinksField(
        {
            'self': 'get_preprint_url',
            'html': 'get_absolute_html_url',
            'doi': 'get_article_doi_url',
            'preprint_doi': 'get_preprint_doi_url',
        }, )

    class Meta:
        type_ = 'preprints'

    def get_preprint_url(self, obj):
        return absolute_reverse(
            'preprints:preprint-detail',
            kwargs={
                'preprint_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

    def get_absolute_url(self, obj):
        return self.get_preprint_url(obj)

    def get_article_doi_url(self, obj):
        return 'https://doi.org/{}'.format(
            obj.article_doi) if obj.article_doi else None

    def get_current_user_permissions(self, obj):
        user = self.context['request'].user
        all_perms = ['read', 'write', 'admin']
        user_perms = []
        for p in all_perms:
            if obj.has_permission(user, p):
                user_perms.append(p)
        return user_perms

    def get_preprint_doi_url(self, obj):
        doi = None
        doi_identifier = obj.get_identifier('doi')
        if doi_identifier:
            doi = doi_identifier.value
        # if a preprint hasn't been published yet, don't show the DOI prematurely
        elif obj.is_published:
            client = obj.get_doi_client()
            doi = client.build_doi(preprint=obj) if client else None
        return 'https://doi.org/{}'.format(doi) if doi else None

    def update(self, preprint, validated_data):
        assert isinstance(
            preprint,
            Preprint), 'You must specify a valid preprint to be updated'

        auth = get_user_auth(self.context['request'])
        if not preprint.has_permission(auth.user, osf_permissions.WRITE):
            raise exceptions.PermissionDenied(
                detail=
                'User must have admin or write permissions to update a preprint.'
            )

        published = validated_data.pop('is_published', None)
        if published and preprint.provider.is_reviewed:
            raise Conflict(
                '{} uses a moderation workflow, so preprints must be submitted for review instead of published directly. Submit a preprint by creating a `submit` Action at {}'
                .format(
                    preprint.provider.name,
                    absolute_reverse(
                        'preprints:preprint-review-action-list',
                        kwargs={
                            'version':
                            self.context['request'].parser_context['kwargs']
                            ['version'],
                            'preprint_id':
                            preprint._id,
                        },
                    ),
                ))

        save_preprint = False
        recently_published = False

        primary_file = validated_data.pop('primary_file', None)
        if primary_file:
            self.set_field(preprint.set_primary_file, primary_file, auth)
            save_preprint = True

        old_tags = set(preprint.tags.values_list('name', flat=True))
        if 'tags' in validated_data:
            current_tags = set(validated_data.pop('tags', []))
        elif self.partial:
            current_tags = set(old_tags)
        else:
            current_tags = set()

        for new_tag in (current_tags - old_tags):
            preprint.add_tag(new_tag, auth=auth)
        for deleted_tag in (old_tags - current_tags):
            preprint.remove_tag(deleted_tag, auth=auth)

        if 'node' in validated_data:
            node = validated_data.pop('node', None)
            self.set_field(preprint.set_supplemental_node, node, auth)
            save_preprint = True

        if 'subjects' in validated_data:
            subjects = validated_data.pop('subjects', None)
            self.set_field(preprint.set_subjects, subjects, auth)
            save_preprint = True

        if 'title' in validated_data:
            title = validated_data['title']
            self.set_field(preprint.set_title, title, auth)
            save_preprint = True

        if 'description' in validated_data:
            description = validated_data['description']
            self.set_field(preprint.set_description, description, auth)
            save_preprint = True

        if 'article_doi' in validated_data:
            preprint.article_doi = validated_data['article_doi']
            save_preprint = True

        if 'license_type' in validated_data or 'license' in validated_data:
            license_details = get_license_details(preprint, validated_data)
            self.set_field(preprint.set_preprint_license, license_details,
                           auth)
            save_preprint = True

        if 'original_publication_date' in validated_data:
            preprint.original_publication_date = validated_data[
                'original_publication_date'] or None
            save_preprint = True

        if published is not None:
            if not preprint.primary_file:
                raise exceptions.ValidationError(
                    detail=
                    'A valid primary_file must be set before publishing a preprint.'
                )
            self.set_field(preprint.set_published, published, auth)
            save_preprint = True
            recently_published = published
            preprint.set_privacy('public', log=False, save=True)

        if save_preprint:
            preprint.save()

        if recently_published:
            for author in preprint.contributors:
                if author != auth.user:
                    project_signals.contributor_added.send(
                        preprint,
                        contributor=author,
                        auth=auth,
                        email_template='preprint')

        return preprint

    def set_field(self, func, val, auth, save=False):
        try:
            func(val, auth)
        except PermissionsError as e:
            raise exceptions.PermissionDenied(detail=str(e))
        except (ValueError, ValidationError, NodeStateError) as e:
            raise exceptions.ValidationError(detail=e.message)
Esempio n. 21
0
class ApiOAuth2PersonalTokenSerializer(JSONAPISerializer):
    """Serialize data about a registered personal access token"""
    def __init__(self, *args, **kwargs):
        super(ApiOAuth2PersonalTokenSerializer, self).__init__(*args, **kwargs)

        request = kwargs['context']['request']

        # Dynamically adding scopes field here, depending on the version
        if expect_scopes_as_relationships(request):
            field = TokenScopesRelationshipField(
                related_view='tokens:token-scopes-list',
                related_view_kwargs={'_id': '<_id>'},
                always_embed=True,
                read_only=False,
            )
            self.fields['scopes'] = field
            self.fields['owner'] = RelationshipField(
                related_view='users:user-detail',
                related_view_kwargs={'user_id': '<owner._id>'},
            )
            # Making scopes embeddable
            self.context['embed']['scopes'] = self.context[
                'view']._get_embed_partial('scopes', field)
        else:
            self.fields['scopes'] = ser.SerializerMethodField()
            self.fields['owner'] = ser.SerializerMethodField()

    id = IDField(
        source='_id',
        read_only=True,
        help_text='The object ID for this token (automatically generated)')
    type = TypeField()

    name = ser.CharField(
        help_text='A short, descriptive name for this token',
        required=True,
    )

    token_id = ser.CharField(read_only=True, allow_blank=True)

    class Meta:
        type_ = 'tokens'

    links = LinksField({
        'html': 'absolute_url',
    })

    def get_owner(self, obj):
        return obj.owner._id

    def get_scopes(self, obj):
        return ' '.join([scope.name for scope in obj.scopes.all()])

    def absolute_url(self, obj):
        return obj.absolute_url

    def get_absolute_url(self, obj):
        return obj.absolute_api_v2_url

    def to_representation(self, obj, envelope='data'):
        data = super(ApiOAuth2PersonalTokenSerializer,
                     self).to_representation(obj, envelope=envelope)
        # Make sure users only see token_id on create
        if not self.context['request'].method == 'POST':
            if 'data' in data:
                data['data']['attributes'].pop('token_id')
            else:
                data['attributes'].pop('token_id')

        return data

    def create(self, validated_data):
        scopes = validate_requested_scopes(validated_data.pop('scopes', None))
        if not scopes:
            raise exceptions.ValidationError(
                'Cannot create a token without scopes.')
        instance = ApiOAuth2PersonalToken(**validated_data)
        try:
            instance.save()
        except ValidationError as e:
            detail = format_validation_error(e)
            raise exceptions.ValidationError(detail=detail)
        for scope in scopes:
            instance.scopes.add(scope)
        return instance

    def update(self, instance, validated_data):
        scopes = validate_requested_scopes(validated_data.pop('scopes', None))
        assert isinstance(instance, ApiOAuth2PersonalToken
                          ), 'instance must be an ApiOAuth2PersonalToken'

        instance.deactivate(
            save=False
        )  # This will cause CAS to revoke the existing token but still allow it to be used in the future, new scopes will be updated properly at that time.
        instance.reload()

        for attr, value in validated_data.items():
            if attr == 'token_id':  # Do not allow user to update token_id
                continue
            else:
                setattr(instance, attr, value)
        if scopes:
            update_scopes(instance, scopes)
        try:
            instance.save()
        except ValidationError as e:
            detail = format_validation_error(e)
            raise exceptions.ValidationError(detail=detail)
        return instance
Esempio n. 22
0
class CollectionSubmissionSerializer(TaxonomizableSerializerMixin, JSONAPISerializer):

    class Meta:
        type_ = 'collected-metadata'

    filterable_fields = frozenset([
        'id',
        'collected_type',
        'date_created',
        'date_modified',
        'subjects',
        'status',
    ])
    id = IDField(source='guid._id', read_only=True)
    type = TypeField()

    creator = RelationshipField(
        related_view='users:user-detail',
        related_view_kwargs={'user_id': '<creator._id>'},
    )
    collection = RelationshipField(
        related_view='collections:collection-detail',
        related_view_kwargs={'collection_id': '<collection._id>'},
    )
    guid = RelationshipField(
        related_view='guids:guid-detail',
        related_view_kwargs={'guids': '<guid._id>'},
        always_embed=True,
    )
    collected_type = ser.CharField(required=False)
    status = ser.CharField(required=False)
    volume = ser.CharField(required=False)
    issue = ser.CharField(required=False)
    program_area = ser.CharField(required=False)

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'collected-metadata:collected-metadata-detail',
            kwargs={
                'collection_id': obj.collection._id,
                'cgm_id': obj.guid._id,
                'version': self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def update(self, obj, validated_data):
        if validated_data and 'subjects' in validated_data:
            auth = get_user_auth(self.context['request'])
            subjects = validated_data.pop('subjects', None)
            try:
                obj.set_subjects(subjects, auth)
            except PermissionsError as e:
                raise exceptions.PermissionDenied(detail=str(e))
            except (ValueError, NodeStateError) as e:
                raise exceptions.ValidationError(detail=str(e))
        if 'status' in validated_data:
            obj.status = validated_data.pop('status')
        if 'collected_type' in validated_data:
            obj.collected_type = validated_data.pop('collected_type')
        if 'volume' in validated_data:
            obj.volume = validated_data.pop('volume')
        if 'issue' in validated_data:
            obj.issue = validated_data.pop('issue')
        if 'program_area' in validated_data:
            obj.program_area = validated_data.pop('program_area')
        obj.save()
        return obj
Esempio n. 23
0
class UserAccountExportSerializer(BaseAPISerializer):
    type = TypeField()

    class Meta:
        type_ = 'user-account-export-form'
Esempio n. 24
0
class CollectionSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'title',
        'date_created',
        'date_modified',
    ])

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    title = ser.CharField(required=True)
    date_created = VersionedDateTimeField(source='created', read_only=True)
    date_modified = VersionedDateTimeField(source='modified', read_only=True)
    bookmarks = ser.BooleanField(read_only=False, default=False, source='is_bookmark_collection')
    is_promoted = ser.BooleanField(read_only=True, default=False)
    is_public = ser.BooleanField(read_only=False, default=False)
    status_choices = ser.ListField(
        child=ser.CharField(max_length=127),
        default=list(),
    )
    collected_type_choices = ser.ListField(
        child=ser.CharField(max_length=127),
        default=list(),
    )
    volume_choices = ser.ListField(
        child=ser.CharField(max_length=127),
        default=list(),
    )
    issue_choices = ser.ListField(
        child=ser.CharField(max_length=127),
        default=list(),
    )
    program_area_choices = ser.ListField(
        child=ser.CharField(max_length=127),
        default=list(),
    )

    links = LinksField({})

    provider = CollectionProviderRelationshipField(
        related_view='providers:collection-providers:collection-provider-detail',
        related_view_kwargs={'provider_id': '<provider._id>'},
        read_only=True,
    )

    node_links = RelationshipField(
        related_view='collections:node-pointers',
        related_view_kwargs={'collection_id': '<_id>'},
        related_meta={'count': 'get_node_links_count'},
    )

    # TODO: Add a self link to this when it's available
    linked_nodes = RelationshipField(
        related_view='collections:linked-nodes',
        related_view_kwargs={'collection_id': '<_id>'},
        related_meta={'count': 'get_node_links_count'},
        self_view='collections:collection-node-pointer-relationship',
        self_view_kwargs={'collection_id': '<_id>'},
    )

    linked_registrations = RelationshipField(
        related_view='collections:linked-registrations',
        related_view_kwargs={'collection_id': '<_id>'},
        related_meta={'count': 'get_registration_links_count'},
        self_view='collections:collection-registration-pointer-relationship',
        self_view_kwargs={'collection_id': '<_id>'},
    )

    linked_preprints = RelationshipField(
        related_view='collections:linked-preprints',
        related_view_kwargs={'collection_id': '<_id>'},
        self_view='collections:collection-preprint-pointer-relationship',
        self_view_kwargs={'collection_id': '<_id>'},
        related_meta={'count': 'get_preprint_links_count'},
    )

    class Meta:
        type_ = 'collections'

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'collections:collection-detail', kwargs={
                'collection_id': obj._id,
                'version': self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_node_links_count(self, obj):
        auth = get_user_auth(self.context['request'])
        node_ids = obj.guid_links.all().values_list('_id', flat=True)
        return Node.objects.filter(guids___id__in=node_ids, is_deleted=False).can_view(user=auth.user, private_link=auth.private_link).count()

    def get_registration_links_count(self, obj):
        auth = get_user_auth(self.context['request'])
        registration_ids = obj.guid_links.all().values_list('_id', flat=True)
        return Registration.objects.filter(guids___id__in=registration_ids, is_deleted=False).can_view(user=auth.user, private_link=auth.private_link).count()

    def get_preprint_links_count(self, obj):
        auth = get_user_auth(self.context['request'])
        return self.context['view'].collection_preprints(obj, auth.user).count()

    def create(self, validated_data):
        node = Collection(**validated_data)
        node.category = ''
        try:
            node.save()
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.messages[0])
        except IntegrityError:
            raise ser.ValidationError('Each user cannot have more than one Bookmark collection.')
        return node

    def update(self, collection, validated_data):
        """Update instance with the validated data.
        """
        assert isinstance(collection, Collection), 'collection must be a Collection'
        if validated_data:
            for key, value in validated_data.items():
                if key == 'title' and collection.is_bookmark_collection:
                    raise InvalidModelValueError('Bookmark collections cannot be renamed.')
                setattr(collection, key, value)
        try:
            collection.save()
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.messages[0])
        return collection
Esempio n. 25
0
class UserEmailsSerializer(JSONAPISerializer):

    filterable_fields = frozenset([
        'confirmed',
        'verified',
        'primary',
    ])

    id = IDField(read_only=True)
    type = TypeField()
    email_address = ser.CharField(source='address')
    confirmed = ser.BooleanField(
        read_only=True,
        help_text='User has clicked the confirmation link in an email.')
    verified = ser.BooleanField(
        required=False,
        help_text=
        'User has verified adding the email on the OSF, i.e. via a modal.')
    primary = ser.BooleanField(required=False)
    is_merge = ser.BooleanField(
        read_only=True,
        required=False,
        help_text='This unconfirmed email is already confirmed to another user.'
    )
    links = LinksField({
        'self': 'get_absolute_url',
        'resend_confirmation': 'get_resend_confirmation_url',
    })

    def get_absolute_url(self, obj):
        user = self.context['request'].user
        return absolute_reverse(
            'users:user-email-detail',
            kwargs={
                'user_id':
                user._id,
                'email_id':
                obj.id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def get_resend_confirmation_url(self, obj):
        if not obj.confirmed:
            url = self.get_absolute_url(obj)
            return '{}?resend_confirmation=true'.format(url)

    class Meta:
        type_ = 'user_emails'

    def create(self, validated_data):
        user = self.context['request'].user
        address = validated_data['address']
        is_merge = Email.objects.filter(address=address).exists()
        if address in user.unconfirmed_emails or address in user.emails.all(
        ).values_list('address', flat=True):
            raise Conflict(
                'This user already has registered with the email address {}'.
                format(address))
        try:
            token = user.add_unconfirmed_email(address)
            user.save()
            if CONFIRM_REGISTRATIONS_BY_EMAIL:
                send_confirm_email(user, email=address)
                user.email_last_sent = timezone.now()
                user.save()
        except ValidationError as e:
            raise exceptions.ValidationError(e.args[0])
        except BlacklistedEmailError:
            raise exceptions.ValidationError(
                'This email address domain is blacklisted.')

        return UserEmail(email_id=token,
                         address=address,
                         confirmed=False,
                         verified=False,
                         primary=False,
                         is_merge=is_merge)

    def update(self, instance, validated_data):
        user = self.context['request'].user
        primary = validated_data.get('primary', None)
        verified = validated_data.get('verified', None)
        if primary and instance.confirmed:
            user.username = instance.address
            user.save()
        elif primary and not instance.confirmed:
            raise exceptions.ValidationError(
                'You cannot set an unconfirmed email address as your primary email address.'
            )

        if verified and not instance.verified:
            if not instance.confirmed:
                raise exceptions.ValidationError(
                    'You cannot verify an email address that has not been confirmed by a user.'
                )
            user.confirm_email(token=instance.id, merge=instance.is_merge)
            instance.verified = True
            instance.is_merge = False
            new_email = Email.objects.get(address=instance.address, user=user)
            instance.id = hashids.encode(new_email.id)
            user.save()

        return instance
Esempio n. 26
0
class NodeSerializer(JSONAPISerializer):
    # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it
    # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this
    # instance
    filterable_fields = frozenset([
        'id', 'title', 'description', 'public', 'tags', 'category',
        'date_created', 'date_modified', 'root', 'parent', 'contributors',
        'preprint'
    ])

    non_anonymized_fields = [
        'id', 'title', 'description', 'category', 'date_created',
        'date_modified', 'registration', 'tags', 'public', 'license', 'links',
        'children', 'comments', 'contributors', 'files', 'node_links',
        'parent', 'root', 'logs', 'wikis'
    ]

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    category_choices = settings.NODE_CATEGORY_MAP.items()
    category_choices_string = ', '.join(
        ["'{}'".format(choice[0]) for choice in category_choices])

    title = ser.CharField(required=True)
    description = ser.CharField(required=False,
                                allow_blank=True,
                                allow_null=True)
    category = ser.ChoiceField(choices=category_choices,
                               help_text='Choices: ' + category_choices_string)
    date_created = DateByVersion(source='created', read_only=True)
    date_modified = DateByVersion(source='last_logged', read_only=True)
    registration = ser.BooleanField(read_only=True, source='is_registration')
    preprint = ser.BooleanField(read_only=True, source='is_preprint')
    fork = ser.BooleanField(read_only=True, source='is_fork')
    collection = ser.BooleanField(read_only=True, source='is_collection')
    tags = JSONAPIListField(child=NodeTagField(), required=False)
    node_license = NodeLicenseSerializer(required=False, source='license')
    template_from = ser.CharField(
        required=False,
        allow_blank=False,
        allow_null=False,
        help_text=
        'Specify a node id for a node you would like to use as a template for the '
        'new node. Templating is like forking, except that you do not copy the '
        'files, only the project structure. Some information is changed on the top '
        'level project by submitting the appropriate fields in the request body, '
        'and some information will not change. By default, the description will '
        'be cleared and the project will be made private.')

    current_user_can_comment = ser.SerializerMethodField(
        help_text='Whether the current user is allowed to post comments')
    current_user_permissions = ser.SerializerMethodField(
        help_text='List of strings representing the permissions '
        'for the current user on this node.')

    # Public is only write-able by admins--see update method
    public = ser.BooleanField(
        source='is_public',
        required=False,
        help_text='Nodes that are made public will give read-only access '
        'to everyone. Private nodes require explicit read '
        'permission. Write and admin access are the same for '
        'public and private nodes. Administrators on a parent '
        'node have implicit read permissions for all child nodes')

    links = LinksField({'html': 'get_absolute_html_url'})
    # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins

    license = NodeLicenseRelationshipField(
        related_view='licenses:license-detail',
        related_view_kwargs={'license_id': '<license.node_license._id>'},
        read_only=False)

    children = RelationshipField(
        related_view='nodes:node-children',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'count': 'get_node_count'},
    )

    comments = RelationshipField(
        related_view='nodes:node-comments',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'unread': 'get_unread_comments_count'},
        filter={'target': '<_id>'})

    contributors = RelationshipField(
        related_view='nodes:node-contributors',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'count': 'get_contrib_count'},
    )

    files = RelationshipField(related_view='nodes:node-providers',
                              related_view_kwargs={'node_id': '<_id>'})

    wikis = RelationshipField(related_view='nodes:node-wikis',
                              related_view_kwargs={'node_id': '<_id>'})

    forked_from = RelationshipField(
        related_view=lambda n: 'registrations:registration-detail'
        if getattr(n, 'is_registration', False) else 'nodes:node-detail',
        related_view_kwargs={'node_id': '<forked_from_guid>'})

    template_node = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<template_node._id>'})

    forks = RelationshipField(related_view='nodes:node-forks',
                              related_view_kwargs={'node_id': '<_id>'})

    node_links = ShowIfVersion(RelationshipField(
        related_view='nodes:node-pointers',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'count': 'get_pointers_count'},
        help_text=
        'This feature is deprecated as of version 2.1. Use linked_nodes instead.'
    ),
                               min_version='2.0',
                               max_version='2.0')

    parent = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<parent_node._id>'},
        filter_key='parent_node')

    identifiers = RelationshipField(related_view='nodes:identifier-list',
                                    related_view_kwargs={'node_id': '<_id>'})

    draft_registrations = HideIfRegistration(
        RelationshipField(related_view='nodes:node-draft-registrations',
                          related_view_kwargs={'node_id': '<_id>'}))

    registrations = HideIfRegistration(
        RelationshipField(related_view='nodes:node-registrations',
                          related_view_kwargs={'node_id': '<_id>'},
                          related_meta={'count': 'get_registration_count'}))

    affiliated_institutions = RelationshipField(
        related_view='nodes:node-institutions',
        related_view_kwargs={'node_id': '<_id>'},
        self_view='nodes:node-relationships-institutions',
        self_view_kwargs={'node_id': '<_id>'})

    root = RelationshipField(related_view='nodes:node-detail',
                             related_view_kwargs={'node_id': '<root._id>'})

    logs = RelationshipField(related_view='nodes:node-logs',
                             related_view_kwargs={'node_id': '<_id>'},
                             related_meta={'count': 'get_logs_count'})

    linked_nodes = RelationshipField(
        related_view='nodes:linked-nodes',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'count': 'get_node_links_count'},
        self_view='nodes:node-pointer-relationship',
        self_view_kwargs={'node_id': '<_id>'},
        self_meta={'count': 'get_node_links_count'})

    linked_registrations = RelationshipField(
        related_view='nodes:linked-registrations',
        related_view_kwargs={'node_id': '<_id>'},
        related_meta={'count': 'get_registration_links_count'},
        self_view='nodes:node-registration-pointer-relationship',
        self_view_kwargs={'node_id': '<_id>'},
        self_meta={'count': 'get_node_links_count'})

    view_only_links = RelationshipField(
        related_view='nodes:node-view-only-links',
        related_view_kwargs={'node_id': '<_id>'},
    )

    citation = RelationshipField(related_view='nodes:node-citation',
                                 related_view_kwargs={'node_id': '<_id>'})

    preprints = HideIfRegistration(
        RelationshipField(related_view='nodes:node-preprints',
                          related_view_kwargs={'node_id': '<_id>'}))

    def get_current_user_permissions(self, obj):
        user = self.context['request'].user
        if user.is_anonymous:
            return ['read']
        permissions = obj.get_permissions(user=user)
        if not permissions:
            permissions = ['read']
        return permissions

    def get_current_user_can_comment(self, obj):
        user = self.context['request'].user
        auth = Auth(user if not user.is_anonymous else None)
        return obj.can_comment(auth)

    class Meta:
        type_ = 'nodes'

    def get_absolute_url(self, obj):
        return obj.get_absolute_url()

    # TODO: See if we can get the count filters into the filter rather than the serializer.

    def get_logs_count(self, obj):
        return obj.logs.count()

    def get_node_count(self, obj):
        auth = get_user_auth(self.context['request'])
        user_id = getattr(auth.user, 'id', None)
        with connection.cursor() as cursor:
            cursor.execute(
                '''
                WITH RECURSIVE parents AS (
                  SELECT parent_id, child_id
                  FROM osf_noderelation
                  WHERE child_id = %s AND is_node_link IS FALSE
                UNION ALL
                  SELECT osf_noderelation.parent_id, parents.parent_id AS child_id
                  FROM parents JOIN osf_noderelation ON parents.PARENT_ID = osf_noderelation.child_id
                  WHERE osf_noderelation.is_node_link IS FALSE
                ), has_admin AS (SELECT * FROM osf_contributor WHERE (node_id IN (SELECT parent_id FROM parents) OR node_id = %s) AND user_id = %s AND admin IS TRUE LIMIT 1)
                SELECT DISTINCT
                  COUNT(child_id)
                FROM
                  osf_noderelation
                JOIN osf_abstractnode ON osf_noderelation.child_id = osf_abstractnode.id
                JOIN osf_contributor ON osf_abstractnode.id = osf_contributor.node_id
                LEFT JOIN osf_privatelink_nodes ON osf_abstractnode.id = osf_privatelink_nodes.abstractnode_id
                LEFT JOIN osf_privatelink ON osf_privatelink_nodes.privatelink_id = osf_privatelink.id
                WHERE parent_id = %s AND is_node_link IS FALSE
                AND osf_abstractnode.is_deleted IS FALSE
                AND (
                  osf_abstractnode.is_public
                  OR (TRUE IN (SELECT TRUE FROM has_admin))
                  OR (osf_contributor.user_id = %s AND osf_contributor.read IS TRUE)
                  OR (osf_privatelink.key = %s AND osf_privatelink.is_deleted = FALSE)
                );
            ''', [obj.id, obj.id, user_id, obj.id, user_id, auth.private_key])

            return int(cursor.fetchone()[0])

    def get_contrib_count(self, obj):
        return len(obj.contributors)

    def get_registration_count(self, obj):
        auth = get_user_auth(self.context['request'])
        registrations = [
            node for node in obj.registrations_all if node.can_view(auth)
        ]
        return len(registrations)

    def get_pointers_count(self, obj):
        return obj.linked_nodes.count()

    def get_node_links_count(self, obj):
        count = 0
        auth = get_user_auth(self.context['request'])
        for pointer in obj.linked_nodes.filter(is_deleted=False).exclude(
                type='osf.collection').exclude(type='osf.registration'):
            if pointer.can_view(auth):
                count += 1
        return count

    def get_registration_links_count(self, obj):
        count = 0
        auth = get_user_auth(self.context['request'])
        for pointer in obj.linked_nodes.filter(
                is_deleted=False,
                type='osf.registration').exclude(type='osf.collection'):
            if pointer.can_view(auth):
                count += 1
        return count

    def get_unread_comments_count(self, obj):
        user = get_user_auth(self.context['request']).user
        node_comments = Comment.find_n_unread(user=user, node=obj, page='node')

        return {'node': node_comments}

    def create(self, validated_data):
        request = self.context['request']
        user = request.user
        Node = apps.get_model('osf.Node')
        tag_instances = []
        if 'tags' in validated_data:
            tags = validated_data.pop('tags')
            for tag in tags:
                tag_instance, created = Tag.objects.get_or_create(
                    name=tag, defaults=dict(system=False))
                tag_instances.append(tag_instance)
        if 'template_from' in validated_data:
            template_from = validated_data.pop('template_from')
            template_node = Node.load(template_from)
            if template_node is None:
                raise exceptions.NotFound
            if not template_node.has_permission(
                    user, 'read', check_parent=False):
                raise exceptions.PermissionDenied

            validated_data.pop('creator')
            changed_data = {template_from: validated_data}
            node = template_node.use_as_template(auth=get_user_auth(request),
                                                 changes=changed_data)
        else:
            node = Node(**validated_data)
        try:
            node.save()
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.messages[0])
        if len(tag_instances):
            for tag in tag_instances:
                node.tags.add(tag)
        if is_truthy(request.GET.get('inherit_contributors')
                     ) and validated_data['parent'].has_permission(
                         user, 'write'):
            auth = get_user_auth(request)
            parent = validated_data['parent']
            contributors = []
            for contributor in parent.contributor_set.exclude(user=user):
                contributors.append({
                    'user':
                    contributor.user,
                    'permissions':
                    parent.get_permissions(contributor.user),
                    'visible':
                    contributor.visible
                })
                if not contributor.user.is_registered:
                    node.add_unregistered_contributor(
                        fullname=contributor.user.fullname,
                        email=contributor.user.email,
                        auth=auth,
                        permissions=parent.get_permissions(contributor.user),
                        existing_user=contributor.user)
            node.add_contributors(contributors, auth=auth, log=True, save=True)
        return node

    def update(self, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node, AbstractNode), 'node must be a Node'
        auth = get_user_auth(self.context['request'])

        # Update tags
        if 'tags' in validated_data:
            new_tags = set(validated_data.pop('tags', []))
            node.update_tags(new_tags, auth=auth)

        if validated_data:

            if 'license_type' in validated_data or 'license' in validated_data:
                license_details = get_license_details(node, validated_data)
                validated_data['node_license'] = license_details

            try:
                node.update(validated_data, auth=auth)
            except ValidationError as e:
                raise InvalidModelValueError(detail=e.message)
            except PermissionsError:
                raise exceptions.PermissionDenied
            except NodeUpdateError as e:
                raise exceptions.ValidationError(detail=e.reason)
            except NodeStateError as e:
                raise InvalidModelValueError(detail=e.message)

        return node
Esempio n. 27
0
class ModeratorSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'full_name',
        'id',
        'permission_group'
    ])

    id = IDField(source='_id', required=False, allow_null=True)
    type = TypeField()
    full_name = ser.CharField(source='fullname', required=False, label='Full name', help_text='Display name used in the general user interface', max_length=186)
    permission_group = ser.CharField(required=True)
    email = ser.EmailField(required=False, write_only=True, validators=[validate_email])

    class Meta:
        type_ = 'moderators'

    def get_absolute_url(self, obj):
        return absolute_reverse('moderators:provider-moderator-detail', kwargs={
            'provider_id': self.context['request'].parser_context['kwargs']['version'],
            'moderator_id': obj._id,
            'version': self.context['request'].parser_context['kwargs']['version']})

    def create(self, validated_data):
        auth = get_user_auth(self.context['request'])
        user_id = validated_data.pop('_id', '')
        address = validated_data.pop('email', '')
        context = {
            'referrer': auth.user
        }
        if user_id and address:
            raise ValidationError('Cannot specify both "id" and "email".')

        user = None
        if user_id:
            user = OSFUser.load(user_id)
        elif address:
            try:
                email = Email.objects.get(address=address.lower())
            except Email.DoesNotExist:
                full_name = validated_data.pop('fullname', '')
                if not full_name:
                    raise ValidationError('"full_name" is required when adding a moderator via email.')
                user = OSFUser.create_unregistered(full_name, email=address)
                user.add_unconfirmed_email(user.username)
                user.save()
                context['confirmation_url'] = user.get_confirmation_url(user.username)
            else:
                user = email.user
        else:
            raise ValidationError('Must specify either "id" or "email".')

        if not user:
            raise ValidationError('Unable to find specified user.')
        context['user'] = user

        provider = self.context['provider']
        if bool(get_perms(user, provider)):
            raise ValidationError('Specified user is already a moderator.')
        context['provider'] = provider
        if 'confirmation_url' in context:
            template = mails.CONFIRM_EMAIL_MODERATION(provider)
        else:
            template = mails.MODERATOR_ADDED(provider)

        perm_group = validated_data.pop('permission_group', '')
        if perm_group not in GROUPS:
            raise ValidationError('Unrecognized permission_group')
        context['role'] = 'an admin' if perm_group == 'admin' else 'a {}'.format(perm_group)
        context['notification_url'] = '{}settings/notifications'.format(DOMAIN)

        provider.add_to_group(user, perm_group)
        setattr(user, 'permission_group', perm_group)  # Allows reserialization
        mails.send_mail(
            user.username,
            template,
            mimetype='html',
            **context
        )
        return user

    def update(self, instance, validated_data):
        provider = self.context['provider']
        perm_group = validated_data.get('permission_group')
        if perm_group == instance.permission_group:
            return instance

        try:
            provider.remove_from_group(instance, instance.permission_group, unsubscribe=False)
        except ValueError as e:
            raise ValidationError(e.message)
        provider.add_to_group(instance, perm_group)
        setattr(instance, 'permission_group', perm_group)
        return instance
Esempio n. 28
0
class CommentSerializer(JSONAPISerializer):

    filterable_fields = frozenset(
        ['deleted', 'date_created', 'date_modified', 'page', 'target'])

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    content = AuthorizedCharField(source='get_content', required=True)
    page = ser.CharField(read_only=True)

    target = TargetField(link_type='related', meta={'type': 'get_target_type'})
    user = RelationshipField(related_view='users:user-detail',
                             related_view_kwargs={'user_id': '<user._id>'})
    reports = RelationshipField(related_view='comments:comment-reports',
                                related_view_kwargs={'comment_id': '<pk>'})

    date_created = DateByVersion(read_only=True)
    date_modified = DateByVersion(read_only=True)
    modified = ser.BooleanField(read_only=True, default=False)
    deleted = ser.BooleanField(read_only=True,
                               source='is_deleted',
                               default=False)
    is_abuse = ser.SerializerMethodField(
        help_text='If the comment has been reported or confirmed.')
    is_ham = ser.SerializerMethodField(
        help_text='Comment has been confirmed as ham.')
    has_report = ser.SerializerMethodField(
        help_text='If the user reported this comment.')
    has_children = ser.SerializerMethodField(
        help_text='Whether this comment has any replies.')
    can_edit = ser.SerializerMethodField(
        help_text='Whether the current user can edit this comment.')

    # LinksField.to_representation adds link to "self"
    links = LinksField({})

    class Meta:
        type_ = 'comments'

    def get_is_ham(self, obj):
        if obj.spam_status == SpamStatus.HAM:
            return True
        return False

    def get_has_report(self, obj):
        user = self.context['request'].user
        if user.is_anonymous():
            return False
        return user._id in obj.reports and not obj.reports[user._id].get(
            'retracted', True)

    def get_is_abuse(self, obj):
        if obj.spam_status == SpamStatus.FLAGGED or obj.spam_status == SpamStatus.SPAM:
            return True
        return False

    def get_can_edit(self, obj):
        user = self.context['request'].user
        if user.is_anonymous():
            return False
        return obj.user._id == user._id and obj.node.can_comment(Auth(user))

    def get_has_children(self, obj):
        return Comment.find(Q('target', 'eq', Guid.load(obj._id))).count() > 0

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'comments:comment-detail',
            kwargs={
                'comment_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

    def update(self, comment, validated_data):
        assert isinstance(comment, Comment), 'comment must be a Comment'
        auth = Auth(self.context['request'].user)

        if validated_data:
            if validated_data.get('is_deleted',
                                  None) is False and comment.is_deleted:
                try:
                    comment.undelete(auth, save=True)
                except PermissionsError:
                    raise PermissionDenied(
                        'Not authorized to undelete this comment.')
            elif validated_data.get('is_deleted',
                                    None) is True and not comment.is_deleted:
                try:
                    comment.delete(auth, save=True)
                except PermissionsError:
                    raise PermissionDenied(
                        'Not authorized to delete this comment.')
            elif 'get_content' in validated_data:
                content = validated_data.pop('get_content')
                try:
                    comment.edit(content, auth=auth, save=True)
                except PermissionsError:
                    raise PermissionDenied(
                        'Not authorized to edit this comment.')
                except ValidationValueError as err:
                    raise ValidationError(err.args[0])
        return comment

    def get_target_type(self, obj):
        if not getattr(obj.referent, 'target_type', None):
            raise InvalidModelValueError(source={
                'pointer':
                '/data/relationships/target/links/related/meta/type'
            },
                                         detail='Invalid comment target type.')
        return obj.referent.target_type

    def sanitize_data(self):
        ret = super(CommentSerializer, self).sanitize_data()
        content = self.validated_data.get('get_content', None)
        if content:
            ret['get_content'] = bleach.clean(content)
        return ret
Esempio n. 29
0
class NodeSerializer(JSONAPISerializer):
    # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it
    # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this
    # instance
    filterable_fields = frozenset([
        'title',
        'description',
        'public',
        'tags',
        'category',
        'date_created',
        'date_modified',
        'registration'
    ])

    id = IDField(source='_id', read_only=True)
    type = TypeField()

    category_choices = Node.CATEGORY_MAP.keys()
    category_choices_string = ', '.join(["'{}'".format(choice) for choice in category_choices])

    title = ser.CharField(required=True)
    description = ser.CharField(required=False, allow_blank=True, allow_null=True)
    category = ser.ChoiceField(choices=category_choices, help_text="Choices: " + category_choices_string)
    date_created = ser.DateTimeField(read_only=True)
    date_modified = ser.DateTimeField(read_only=True)
    registration = ser.BooleanField(read_only=True, source='is_registration')
    fork = ser.BooleanField(read_only=True, source='is_fork')
    collection = DevOnly(ser.BooleanField(read_only=True, source='is_folder'))
    dashboard = ser.BooleanField(read_only=True, source='is_dashboard')
    tags = JSONAPIListField(child=NodeTagField(), required=False)

    # Public is only write-able by admins--see update method
    public = ser.BooleanField(source='is_public', required=False,
                              help_text='Nodes that are made public will give read-only access '
                                        'to everyone. Private nodes require explicit read '
                                        'permission. Write and admin access are the same for '
                                        'public and private nodes. Administrators on a parent '
                                        'node have implicit read permissions for all child nodes')

    links = LinksField({'html': 'get_absolute_url'})
    # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins

    children = RelationshipField(
        related_view='nodes:node-children',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_node_count'},
    )

    comments = RelationshipField(
        related_view='nodes:node-comments',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'unread': 'get_unread_comments_count'})

    contributors = RelationshipField(
        related_view='nodes:node-contributors',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_contrib_count'},
    )

    files = RelationshipField(
        related_view='nodes:node-providers',
        related_view_kwargs={'node_id': '<pk>'}
    )

    forked_from = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<forked_from_id>'}
    )

    node_links = DevOnly(RelationshipField(
        related_view='nodes:node-pointers',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_pointers_count'},
    ))

    parent = RelationshipField(
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<parent_id>'}
    )

    registrations = DevOnly(HideIfRegistration(RelationshipField(
        related_view='nodes:node-registrations',
        related_view_kwargs={'node_id': '<pk>'},
        related_meta={'count': 'get_registration_count'}
    )))

    logs = RelationshipField(
        related_view='nodes:node-logs',
        related_view_kwargs={'node_id': '<pk>'},
    )

    class Meta:
        type_ = 'nodes'

    def get_absolute_url(self, obj):
        return obj.absolute_url

    # TODO: See if we can get the count filters into the filter rather than the serializer.

    def get_user_auth(self, request):
        user = request.user
        if user.is_anonymous():
            auth = Auth(None)
        else:
            auth = Auth(user)
        return auth

    def get_node_count(self, obj):
        auth = self.get_user_auth(self.context['request'])
        nodes = [node for node in obj.nodes if node.can_view(auth) and node.primary and not node.is_deleted]
        return len(nodes)

    def get_contrib_count(self, obj):
        return len(obj.contributors)

    def get_registration_count(self, obj):
        auth = self.get_user_auth(self.context['request'])
        registrations = [node for node in obj.node__registrations if node.can_view(auth)]
        return len(registrations)

    def get_pointers_count(self, obj):
        return len(obj.nodes_pointer)

    def get_unread_comments_count(self, obj):
        auth = self.get_user_auth(self.context['request'])
        user = auth.user
        return Comment.find_unread(user=user, node=obj)

    def create(self, validated_data):
        node = Node(**validated_data)
        try:
            node.save()
        except ValidationValueError as e:
            raise InvalidModelValueError(detail=e.message)
        return node

    def update(self, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node, Node), 'node must be a Node'
        auth = self.get_user_auth(self.context['request'])
        old_tags = set([tag._id for tag in node.tags])
        if 'tags' in validated_data:
            current_tags = set(validated_data.get('tags'))
            del validated_data['tags']
        elif self.partial:
            current_tags = set(old_tags)
        else:
            current_tags = set()

        for new_tag in (current_tags - old_tags):
            node.add_tag(new_tag, auth=auth)
        for deleted_tag in (old_tags - current_tags):
            node.remove_tag(deleted_tag, auth=auth)

        if validated_data:
            try:
                node.update(validated_data, auth=auth)
            except ValidationValueError as e:
                raise InvalidModelValueError(detail=e.message)
            except PermissionsError:
                raise exceptions.PermissionDenied

        return node
Esempio n. 30
0
class WikiSerializer(JSONAPISerializer):

    filterable_fields = frozenset(['name', 'date_modified'])

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    name = ser.CharField(source='page_name')
    kind = ser.SerializerMethodField()
    size = ser.SerializerMethodField()
    path = ser.SerializerMethodField()
    materialized_path = ser.SerializerMethodField(method_name='get_path')
    date_modified = ser.DateTimeField(source='date')
    content_type = ser.SerializerMethodField()
    current_user_can_comment = ser.SerializerMethodField(
        help_text='Whether the current user is allowed to post comments')
    extra = ser.SerializerMethodField(
        help_text='Additional metadata about this wiki')

    user = RelationshipField(related_view='users:user-detail',
                             related_view_kwargs={'user_id': '<user._id>'})

    # LinksField.to_representation adds link to "self"
    links = LinksField({
        'info':
        Link('wikis:wiki-detail', kwargs={'wiki_id': '<_id>'}),
        'download':
        'get_wiki_content'
    })

    class Meta:
        type_ = 'wikis'

    def get_absolute_url(self, obj):
        return obj.get_absolute_url()

    def get_path(self, obj):
        return '/{}'.format(obj)

    def get_kind(self, obj):
        return 'file'

    def get_size(self, obj):
        return sys.getsizeof(obj.content)

    def get_current_user_can_comment(self, obj):
        user = self.context['request'].user
        auth = Auth(user if not user.is_anonymous() else None)
        return obj.node.can_comment(auth)

    def get_content_type(self, obj):
        return 'text/markdown'

    def get_extra(self, obj):
        return {'version': obj.version}

    def get_wiki_content(self, obj):
        return absolute_reverse(
            'wikis:wiki-content',
            kwargs={
                'wiki_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })