Esempio n. 1
0
class DraftRegistrationSerializer(JSONAPISerializer):

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    registration_supplement = ser.CharField(source='registration_schema._id',
                                            required=True)
    registration_metadata = ser.DictField(required=False)
    datetime_initiated = VersionedDateTimeField(read_only=True)
    datetime_updated = VersionedDateTimeField(read_only=True)

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

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

    registration_schema = RelationshipField(
        related_view='metaschemas:metaschema-detail',
        related_view_kwargs={'metaschema_id': '<registration_schema._id>'})

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

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

    def create(self, validated_data):
        node = validated_data.pop('node')
        initiator = validated_data.pop('initiator')
        metadata = validated_data.pop('registration_metadata', None)

        schema_id = validated_data.pop('registration_schema').get('_id')
        schema = get_object_or_error(MetaSchema, schema_id,
                                     self.context['request'])
        if schema.schema_version != LATEST_SCHEMA_VERSION or not schema.active:
            raise exceptions.ValidationError(
                'Registration supplement must be an active schema.')

        draft = DraftRegistration.create_from_node(node=node,
                                                   user=initiator,
                                                   schema=schema)
        reviewer = is_prereg_admin_not_project_admin(self.context['request'],
                                                     draft)

        if metadata:
            try:
                # Required fields are only required when creating the actual registration, not updating the draft.
                draft.validate_metadata(metadata=metadata,
                                        reviewer=reviewer,
                                        required_fields=False)
            except ValidationError as e:
                raise exceptions.ValidationError(e.message)
            draft.update_metadata(metadata)
            draft.save()
        return draft

    class Meta:
        type_ = 'draft_registrations'
Esempio n. 2
0
class NodeRequestSerializer(JSONAPISerializer):
    class Meta:
        type_ = 'node-requests'

    filterable_fields = frozenset(
        ['creator', 'request_type', 'machine_state', 'created', 'id'])
    id = ser.CharField(source='_id', read_only=True)
    request_type = ser.ChoiceField(read_only=True,
                                   required=False,
                                   choices=RequestTypes.choices())
    machine_state = ser.ChoiceField(read_only=True,
                                    required=False,
                                    choices=DefaultStates.choices())
    comment = ser.CharField(required=False, allow_blank=True, max_length=65535)
    created = VersionedDateTimeField(read_only=True)
    modified = VersionedDateTimeField(read_only=True)
    date_last_transitioned = VersionedDateTimeField(read_only=True)

    target = RelationshipField(
        read_only=True,
        related_view='nodes:node-detail',
        related_view_kwargs={'node_id': '<target._id>'},
        filter_key='target___id',
    )

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

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

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

    def get_target_url(self, obj):
        return absolute_reverse(
            'nodes:node-detail',
            kwargs={
                'node_id':
                obj.target._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

    def create(self, validated_data):
        raise NotImplementedError()
class MeetingSerializer(JSONAPISerializer):

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

    id = IDField(source='endpoint', read_only=True)
    type = TypeField()
    name = ser.CharField(read_only=True)
    location = ser.CharField(read_only=True)
    start_date = VersionedDateTimeField(read_only=True)
    end_date = VersionedDateTimeField(read_only=True)
    info_url = ser.URLField(read_only=True)
    logo_url = ser.URLField(read_only=True)
    field_names = ser.DictField(read_only=True)
    submissions_count = ser.SerializerMethodField()
    active = ser.BooleanField(read_only=True)
    type_one_submission_email = ser.SerializerMethodField()
    type_two_submission_email = ser.SerializerMethodField()
    is_accepting_type_one = ser.BooleanField(source='poster', read_only=True)
    is_accepting_type_two = ser.BooleanField(source='talk', read_only=True)

    submissions = RelationshipField(
        related_view='meetings:meeting-submissions',
        related_view_kwargs={'meeting_id': '<endpoint>'},
        related_meta={'count': 'get_submissions_count'},
    )

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

    def format_submission_email(self, obj, submission_field):
        if obj.active:
            return '{}-{}@osf.io'.format(obj.endpoint,
                                         obj.field_names.get(submission_field))
        return ''

    def get_type_one_submission_email(self, obj):
        return self.format_submission_email(obj, 'submission1')

    def get_type_two_submission_email(self, obj):
        return self.format_submission_email(obj, 'submission2')

    def get_absolute_url(self, obj):
        return absolute_reverse('meetings:meeting-detail',
                                kwargs={'meeting_id': obj.endpoint})

    def get_submissions_count(self, obj):
        if getattr(obj, 'submissions_count', None):
            return obj.submissions_count
        else:
            return obj.valid_submissions.count()

    class Meta:
        type_ = 'meetings'
Esempio n. 4
0
class RegistrationSerializer(BaseRegistrationSerializer):
    """
    Overrides BaseRegistrationSerializer to add draft_registration, registration_choice, and lift_embargo fields
    """
    draft_registration = ser.CharField(write_only=True)
    registration_choice = ser.ChoiceField(write_only=True, choices=['immediate', 'embargo'])
    lift_embargo = VersionedDateTimeField(write_only=True, default=None, input_formats=['%Y-%m-%dT%H:%M:%S'])
Esempio n. 5
0
class NodeLogSerializer(JSONAPISerializer):

    filterable_fields = frozenset(['action', 'date', 'user'])
    non_anonymized_fields = [
        'id',
        'date',
        'action',
    ]

    id = ser.CharField(read_only=True, source='_id')
    date = VersionedDateTimeField(read_only=True)
    action = ser.CharField(read_only=True)
    params = ser.SerializerMethodField(read_only=True)
    links = LinksField({'self': 'get_absolute_url'})

    class Meta:
        type_ = 'logs'

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

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

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

    # This would be a node_link, except that data isn't stored in the node log params
    linked_node = HideIfNotNodePointerLog(
        RelationshipField(
            related_view='nodes:node-detail',
            related_view_kwargs={'node_id': '<params.pointer.id>'}))

    linked_registration = HideIfNotRegistrationPointerLog(
        RelationshipField(
            related_view='registrations:registration-detail',
            related_view_kwargs={'node_id': '<params.pointer.id>'}))

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

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

    def get_params(self, obj):
        if obj.action == 'osf_storage_folder_created' and obj.params.get(
                'urls'):
            obj.params.pop('urls')
        return NodeLogParamsSerializer(obj.params,
                                       context=self.context,
                                       read_only=True).data
Esempio n. 6
0
class FileVersionSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'size',
        'identifier',
        'content_type',
    ])
    id = ser.CharField(read_only=True, source='identifier')
    size = ser.IntegerField(read_only=True, help_text='The size of this file at this version')
    content_type = ser.CharField(read_only=True, help_text='The mime type of this file at this verison')
    date_created = VersionedDateTimeField(source='created', read_only=True, help_text='The date that this version was created')
    links = LinksField({
        'self': 'self_url',
        'html': 'absolute_url'
    })

    class Meta:
        type_ = 'file_versions'

    def self_url(self, obj):
        return absolute_reverse('files:version-detail', kwargs={
            'version_id': obj.identifier,
            'file_id': self.context['view'].kwargs['file_id'],
            'version': self.context['request'].parser_context['kwargs']['version']
        })

    def absolute_url(self, obj):
        fobj = self.context['view'].get_file()
        return furl.furl(settings.DOMAIN).set(
            path=(fobj.node._id, 'files', fobj.provider, fobj.path.lstrip('/')),
            query={fobj.version_identifier: obj.identifier}  # TODO this can probably just be changed to revision or version
        ).url

    def get_absolute_url(self, obj):
        return self.self_url(obj)
Esempio n. 7
0
class NodeForksSerializer(NodeSerializer):

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

    title = ser.CharField(required=False)
    category = ser.ChoiceField(read_only=True,
                               choices=category_choices,
                               help_text='Choices: ' + category_choices_string)
    forked_date = VersionedDateTimeField(read_only=True)

    def create(self, validated_data):
        node = validated_data.pop('node')
        fork_title = validated_data.pop('title', None)
        request = self.context['request']
        auth = get_user_auth(request)
        fork = node.fork_node(auth, title=fork_title)

        try:
            fork.save()
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.message)

        return fork
Esempio n. 8
0
class ViewOnlyLinkDetailSerializer(JSONAPISerializer):
    key = ser.CharField(read_only=True)
    id = IDField(source='_id', read_only=True)
    date_created = VersionedDateTimeField(source='created', read_only=True)
    anonymous = ser.BooleanField(required=False)
    name = ser.CharField(required=False)

    nodes = RelationshipField(
        related_view='view-only-links:view-only-link-nodes',
        related_view_kwargs={'link_id': '<_id>'},
        self_view='view-only-links:view-only-link-nodes-relationships',
        self_view_kwargs={'link_id': '<_id>'}
    )

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

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'nodes:node-view-only-link-detail',
            kwargs={
                'link_id': obj._id,
                'version': self.context['request'].parser_context['kwargs']['version']
            }
        )

    class Meta:
        type_ = 'view-only-links'
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)
        try:
            instance.save()
        except ValidationError as e:
            detail = format_validation_error(e)
            raise exceptions.ValidationError(detail=detail)
        return instance
Esempio n. 10
0
class BannerSerializer(JSONAPISerializer):

    start_date = VersionedDateTimeField(read_only=True)
    end_date = VersionedDateTimeField(read_only=True)
    color = ser.CharField(read_only=True)
    license = ser.CharField(read_only=True)
    link = ser.URLField(read_only=True)
    name = ser.CharField(read_only=True)
    default_alt_text = ser.SerializerMethodField()
    mobile_alt_text = ser.SerializerMethodField()

    links = LinksField({
        'self': 'get_absolute_url',
        'default_photo': 'get_default_photo_url',
        'mobile_photo': 'get_mobile_photo_url',
    })

    def get_default_photo_url(self, banner):
        if banner.default_photo:
            return banner.default_photo.url

    def get_mobile_photo_url(self, banner):
        if banner.mobile_photo:
            return banner.mobile_photo.url

    def get_default_alt_text(self, banner):
        return self.add_license(banner, banner.default_alt_text)

    def get_mobile_alt_text(self, banner):
        if banner.mobile_alt_text:
            return self.add_license(banner, banner.mobile_alt_text)
        return self.get_default_alt_text(banner)

    def add_license(self, banner, text):
        if banner.license and not banner.license.lower() == 'none':
            return text + ' Image copyright {}.'.format(banner.license)
        return text

    # Only the current banner's URL is surfaced through the API
    # Individual banners are not accessible publicly
    def get_absolute_url(self, obj):
        return absolute_reverse('banners:current')

    class Meta:
        type_ = 'banners'
class GroupSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'name',
    ])

    non_anonymized_fields = [
        'type',
    ]

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    name = ser.CharField(required=True)
    date_created = VersionedDateTimeField(source='created', read_only=True)
    date_modified = VersionedDateTimeField(source='modified', read_only=True)

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

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

    members = RelationshipField(
        related_view='groups:group-members',
        related_view_kwargs={'group_id': '<_id>'},
    )

    class Meta:
        type_ = 'groups'

    def create(self, validated_data):
        group = OSFGroup(creator=validated_data['creator'],
                         name=validated_data['name'])
        group.save()
        return group

    def update(self, instance, validated_data):
        if 'name' in validated_data:
            instance.set_group_name(validated_data.get('name'))
            instance.save()
        return instance
Esempio n. 12
0
class NodeViewOnlyLinkSerializer(JSONAPISerializer):
    filterable_fields = frozenset(['anonymous', 'name', 'date_created'])

    key = ser.CharField(read_only=True)
    id = IDField(source='_id', read_only=True)
    date_created = VersionedDateTimeField(source='created', read_only=True)
    anonymous = ser.BooleanField(required=False, default=False)
    name = ser.CharField(required=False, default='Shared project link')

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

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

    nodes = RelationshipField(
        related_view='view-only-links:view-only-link-nodes',
        related_view_kwargs={'link_id': '<_id>'},
        self_view='view-only-links:view-only-link-nodes-relationships',
        self_view_kwargs={'link_id': '<_id>'})

    def create(self, validated_data):
        name = validated_data.pop('name')
        user = get_user_auth(self.context['request']).user
        anonymous = validated_data.pop('anonymous')
        node = self.context['view'].get_node()

        try:
            view_only_link = new_private_link(name=name,
                                              user=user,
                                              nodes=[node],
                                              anonymous=anonymous)
        except ValidationError:
            raise exceptions.ValidationError('Invalid link name.')

        return view_only_link

    def get_absolute_url(self, obj):
        return absolute_reverse(
            'nodes:node-view-only-link-detail',
            kwargs={
                'link_id':
                obj._id,
                'node_id':
                self.context['request'].parser_context['kwargs']['node_id'],
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

    class Meta:
        type_ = 'view_only_links'
Esempio n. 13
0
class CitationSerializer(JSONAPISerializer):
    filterable_fields = frozenset(['title', 'short_title', 'summary', 'id'])
    id = ser.CharField(source='_id', required=True)
    title = ser.CharField(max_length=200)
    date_parsed = VersionedDateTimeField(
        read_only=True, help_text='Datetime the csl file was last parsed')

    short_title = ser.CharField(max_length=500)
    summary = ser.CharField(max_length=200)

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

    class Meta:
        type_ = 'citation-styles'
Esempio n. 14
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. 15
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. 16
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. 17
0
class BaseRegistrationSerializer(NodeSerializer):

    title = ser.CharField(read_only=True)
    description = ser.CharField(read_only=True)
    category_choices = NodeSerializer.category_choices
    category_choices_string = NodeSerializer.category_choices_string
    category = HideIfWithdrawal(
        ser.ChoiceField(read_only=True,
                        choices=category_choices,
                        help_text='Choices: ' + category_choices_string))
    date_modified = VersionedDateTimeField(source='last_logged',
                                           read_only=True)
    fork = HideIfWithdrawal(ser.BooleanField(read_only=True, source='is_fork'))
    collection = HideIfWithdrawal(
        ser.BooleanField(read_only=True, source='is_collection'))
    access_requests_enabled = HideIfWithdrawal(
        ser.BooleanField(read_only=True))
    node_license = HideIfWithdrawal(NodeLicenseSerializer(read_only=True))
    tags = HideIfWithdrawal(
        ValuesListField(attr_name='name',
                        child=ser.CharField(),
                        required=False))
    public = HideIfWithdrawal(
        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'))
    current_user_permissions = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text='List of strings representing the permissions '
            'for the current user on this node.'))

    pending_embargo_approval = HideIfWithdrawal(
        ser.BooleanField(
            read_only=True,
            source='is_pending_embargo',
            help_text=
            'The associated Embargo is awaiting approval by project admins.'))
    pending_registration_approval = HideIfWithdrawal(
        ser.BooleanField(
            source='is_pending_registration',
            read_only=True,
            help_text=
            'The associated RegistrationApproval is awaiting approval by project admins.'
        ))
    pending_withdrawal = HideIfWithdrawal(
        ser.BooleanField(
            source='is_pending_retraction',
            read_only=True,
            help_text=
            'The registration is awaiting withdrawal approval by project admins.'
        ))
    withdrawn = ser.BooleanField(
        source='is_retracted',
        read_only=True,
        help_text='The registration has been withdrawn.')

    date_registered = VersionedDateTimeField(
        source='registered_date',
        read_only=True,
        help_text='Date time of registration.')
    date_withdrawn = VersionedDateTimeField(
        source='retraction.date_retracted',
        read_only=True,
        help_text='Date time of when this registration was retracted.')
    embargo_end_date = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text='When the embargo on this registration will be lifted.'))

    withdrawal_justification = ser.CharField(source='retraction.justification',
                                             read_only=True)
    template_from = HideIfWithdrawal(
        ser.CharField(
            read_only=True,
            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.'))
    registration_supplement = ser.SerializerMethodField()
    registered_meta = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text=
            'A dictionary with supplemental registration questions and responses.'
        ))

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

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

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

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

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

    implicit_contributors = RelationshipField(
        related_view='registrations:registration-implicit-contributors',
        related_view_kwargs={'node_id': '<_id>'},
        help_text=
        'This feature is experimental and being tested. It may be deprecated.')

    files = HideIfWithdrawal(
        RelationshipField(related_view='registrations:registration-providers',
                          related_view_kwargs={'node_id': '<_id>'}))

    wikis = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-wikis',
            related_view_kwargs={'node_id': '<_id>'},
        ))

    forked_from = HideIfWithdrawal(
        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 = HideIfWithdrawal(
        RelationshipField(
            related_view='nodes:node-detail',
            related_view_kwargs={'node_id': '<template_node._id>'}))

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

    logs = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-logs',
            related_view_kwargs={'node_id': '<_id>'},
        ))

    forks = HideIfWithdrawal(
        RelationshipField(related_view='registrations:registration-forks',
                          related_view_kwargs={'node_id': '<_id>'}))

    node_links = ShowIfVersion(HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-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 = HideIfWithdrawal(
        RelationshipField(related_view='registrations:registration-detail',
                          related_view_kwargs={'node_id': '<parent_node._id>'},
                          filter_key='parent_node'))

    root = HideIfWithdrawal(
        RelationshipField(related_view='registrations:registration-detail',
                          related_view_kwargs={'node_id': '<root._id>'}))

    affiliated_institutions = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-institutions',
            related_view_kwargs={'node_id': '<_id>'}))

    registration_schema = RelationshipField(
        related_view='metaschemas:registration-metaschema-detail',
        related_view_kwargs={'metaschema_id': '<registered_schema_id>'})

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

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

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

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

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

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

    view_only_links = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-view-only-links',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_view_only_links_count'},
        ))

    citation = HideIfWithdrawal(
        RelationshipField(related_view='registrations:registration-citation',
                          related_view_kwargs={'node_id': '<_id>'}))

    links = LinksField({
        'self': 'get_registration_url',
        'html': 'get_absolute_html_url'
    })

    def get_registration_url(self, obj):
        return absolute_reverse(
            'registrations:registration-detail',
            kwargs={
                'node_id':
                obj._id,
                'version':
                self.context['request'].parser_context['kwargs']['version']
            })

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

    def create(self, validated_data):
        auth = get_user_auth(self.context['request'])
        draft = validated_data.pop('draft')
        registration_choice = validated_data.pop('registration_choice',
                                                 'immediate')
        embargo_lifted = validated_data.pop('lift_embargo', None)
        reviewer = is_prereg_admin_not_project_admin(self.context['request'],
                                                     draft)

        try:
            draft.validate_metadata(metadata=draft.registration_metadata,
                                    reviewer=reviewer,
                                    required_fields=True)
        except ValidationValueError as e:
            raise exceptions.ValidationError(e.message)

        registration = draft.register(auth, save=True)

        if registration_choice == 'embargo':
            if not embargo_lifted:
                raise exceptions.ValidationError(
                    'lift_embargo must be specified.')
            embargo_end_date = embargo_lifted.replace(tzinfo=pytz.utc)
            try:
                registration.embargo_registration(auth.user, embargo_end_date)
            except ValidationError as err:
                raise exceptions.ValidationError(err.message)
        else:
            try:
                registration.require_approval(auth.user)
            except NodeStateError as err:
                raise exceptions.ValidationError(err)

        registration.save()
        return registration

    def get_registered_meta(self, obj):
        if obj.registered_meta:
            meta_values = obj.registered_meta.values()[0]
            try:
                return json.loads(meta_values)
            except TypeError:
                return meta_values
            except ValueError:
                return meta_values
        return None

    def get_embargo_end_date(self, obj):
        if obj.embargo_end_date:
            return obj.embargo_end_date
        return None

    def get_registration_supplement(self, obj):
        if obj.registered_schema:
            schema = obj.registered_schema.first()
            if schema is None:
                return None
            return schema.name
        return None

    def get_current_user_permissions(self, obj):
        return NodeSerializer.get_current_user_permissions(self, obj)

    def update(self, registration, validated_data):
        auth = Auth(self.context['request'].user)
        # Update tags
        if 'tags' in validated_data:
            new_tags = validated_data.pop('tags', [])
            try:
                registration.update_tags(new_tags, auth=auth)
            except NodeStateError as err:
                raise Conflict(err.message)

        is_public = validated_data.get('is_public', None)
        if is_public is not None:
            if is_public:
                try:
                    registration.update(validated_data, auth=auth)
                except NodeUpdateError as err:
                    raise exceptions.ValidationError(err.reason)
                except NodeStateError as err:
                    raise exceptions.ValidationError(err.message)
            else:
                raise exceptions.ValidationError(
                    'Registrations can only be turned from private to public.')
        return registration

    class Meta:
        type_ = 'registrations'
Esempio n. 18
0
class RegistrationCreateSerializer(RegistrationSerializer):
    """
    Overrides RegistrationSerializer to add draft_registration, registration_choice, and lift_embargo fields
    """
    draft_registration = ser.CharField(write_only=True)
    registration_choice = ser.ChoiceField(write_only=True,
                                          choices=['immediate', 'embargo'])
    lift_embargo = VersionedDateTimeField(write_only=True,
                                          default=None,
                                          input_formats=['%Y-%m-%dT%H:%M:%S'])
    children = ser.ListField(write_only=True, required=False)

    def create(self, validated_data):
        auth = get_user_auth(self.context['request'])
        draft = validated_data.pop('draft')
        registration_choice = validated_data.pop('registration_choice',
                                                 'immediate')
        embargo_lifted = validated_data.pop('lift_embargo', None)
        reviewer = is_prereg_admin_not_project_admin(self.context['request'],
                                                     draft)
        children = validated_data.pop('children', [])
        if children:
            # First check that all children are valid
            child_nodes = Node.objects.filter(guids___id__in=children)
            if child_nodes.count() != len(children):
                raise exceptions.ValidationError(
                    'Some child nodes could not be found.')

        # Second check that metadata doesn't have files that are not in the child nodes being registered.
        registering = children + [draft.branched_from._id]
        orphan_files = self._find_orphan_files(registering, draft)
        if orphan_files:
            orphan_files_names = [
                file_data['selectedFileName'] for file_data in orphan_files
            ]
            raise exceptions.ValidationError(
                'All files attached to this form must be registered to complete the process. '
                'The following file(s) are attached, but are not part of a component being'
                ' registered: {}'.format(', '.join(orphan_files_names)))

        try:
            draft.validate_metadata(metadata=draft.registration_metadata,
                                    reviewer=reviewer,
                                    required_fields=True)
        except ValidationValueError:
            log_exception(
            )  # Probably indicates a bug on our end, so log to sentry
            # TODO: Raise an error once our JSON schemas are updated

        try:
            registration = draft.register(auth, save=True, child_ids=children)
        except NodeStateError as err:
            raise exceptions.ValidationError(err)

        if registration_choice == 'embargo':
            if not embargo_lifted:
                raise exceptions.ValidationError(
                    'lift_embargo must be specified.')
            embargo_end_date = embargo_lifted.replace(tzinfo=pytz.utc)
            try:
                registration.embargo_registration(auth.user, embargo_end_date)
            except ValidationError as err:
                raise exceptions.ValidationError(err.message)
        else:
            try:
                registration.require_approval(auth.user)
            except NodeStateError as err:
                raise exceptions.ValidationError(err)

        registration.save()
        return registration

    def _find_orphan_files(self, registering, draft):
        from website.archiver.utils import find_selected_files
        files = find_selected_files(draft.registration_schema,
                                    draft.registration_metadata)
        orphan_files = []
        for _, value in files.items():
            if 'extra' in value:
                for file_metadata in value['extra']:
                    if file_metadata['nodeId'] not in registering:
                        orphan_files.append(file_metadata)
        return orphan_files
Esempio n. 19
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', allow_blank=False)
    kind = ser.SerializerMethodField()
    size = ser.SerializerMethodField()
    path = ser.SerializerMethodField()
    content = ser.CharField(write_only=True, required=False, allow_blank=False)
    materialized_path = ser.SerializerMethodField(method_name='get_path')
    date_modified = VersionedDateTimeField(source='modified', read_only=True)
    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._id)

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

    def get_size(self, obj):
        return sys.getsizeof(obj.get_version().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.get_version().identifier,
        }

    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'],
            },
        )
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 PreprintSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'date_created',
        'date_modified',
        'date_published',
        'original_publication_date',
        'provider',
        'is_published',
        'subjects',
        'reviews_state',
        'node_is_public',
    ])

    id = IDField(source='_id', read_only=True)
    subjects = ser.SerializerMethodField()
    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)
    doi = ser.CharField(source='article_doi', required=False, allow_null=True)
    is_published = ser.BooleanField(required=False)
    is_preprint_orphan = ser.BooleanField(read_only=True)
    license_record = NodeLicenseSerializer(required=False, source='license')
    title = ser.CharField(source='node.title', required=False)
    description = ser.CharField(required=False,
                                allow_blank=True,
                                allow_null=True,
                                source='node.description')
    tags = JSONAPIListField(child=NodeTagField(),
                            required=False,
                            source='node.tags')
    node_is_public = ser.BooleanField(read_only=True, source='node__is_public')
    preprint_doi_created = VersionedDateTimeField(read_only=True)

    contributors = RelationshipField(
        related_view='nodes:node-contributors',
        related_view_kwargs={'node_id': '<node._id>'},
    )
    reviews_state = ser.CharField(source='machine_state',
                                  read_only=True,
                                  max_length=15)
    date_last_transitioned = VersionedDateTimeField(read_only=True)

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

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

    node = NodeRelationshipField(related_view='nodes:node-detail',
                                 related_view_kwargs={'node_id': '<node._id>'},
                                 read_only=False)

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

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

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

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

    review_actions = RelationshipField(
        related_view='preprints:preprint-review-action-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_subjects(self, obj):
        return [[TaxonomyField().to_representation(subj) for subj in hier]
                for hier in obj.subject_hierarchy]

    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://dx.doi.org/{}'.format(
            obj.article_doi) if obj.article_doi else None

    def get_preprint_doi_url(self, obj):
        doi_identifier = obj.get_identifier('doi')
        if doi_identifier:
            return 'https://dx.doi.org/{}'.format(doi_identifier.value)
        else:
            built_identifier = settings.EZID_FORMAT.format(
                namespace=settings.DOI_NAMESPACE,
                guid=obj._id).replace('doi:', '').upper()
            return 'https://dx.doi.org/{}'.format(
                built_identifier
            ) if built_identifier and obj.is_published else None

    def run_validation(self, *args, **kwargs):
        # Overrides construtor for validated_data to allow writes to a SerializerMethodField
        # Validation for `subjects` happens in the model
        _validated_data = super(PreprintSerializer,
                                self).run_validation(*args, **kwargs)
        if 'subjects' in self.initial_data:
            _validated_data['subjects'] = self.initial_data['subjects']
        return _validated_data

    def update(self, preprint, validated_data):
        assert isinstance(
            preprint,
            PreprintService), 'You must specify a valid preprint to be updated'
        assert isinstance(
            preprint.node, Node
        ), 'You must specify a preprint with a valid node to be updated.'

        auth = get_user_auth(self.context['request'])
        if not preprint.node.has_permission(auth.user, 'admin'):
            raise exceptions.PermissionDenied(
                detail='User must be an admin 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_node = False
        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_node = True

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

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

        if 'node' in validated_data:
            preprint.node.update(fields=validated_data.pop('node'))
            save_node = True

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

        if 'article_doi' in validated_data:
            preprint.node.preprint_article_doi = validated_data['article_doi']
            save_node = 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']
            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.node.set_privacy('public')
            save_node = True

        if save_node:
            try:
                preprint.node.save()
            except ValidationError as e:
                # Raised from invalid DOI
                raise exceptions.ValidationError(detail=e.messages[0])

        if save_preprint:
            preprint.save()

        # Send preprint confirmation email signal to new authors on preprint! -- only when published
        # TODO: Some more thought might be required on this; preprints made from existing
        # nodes will send emails making it seem like a new node.
        if recently_published:
            for author in preprint.node.contributors:
                if author != auth.user:
                    project_signals.contributor_added.send(
                        preprint.node,
                        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=e.message)
        except (ValueError, ValidationError, NodeStateError) as e:
            raise exceptions.ValidationError(detail=e.message)
Esempio n. 22
0
class CommentSerializer(JSONAPISerializer):

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

    id = IDField(source='_id', read_only=True)
    type = TypeField()
    content = AnonymizedRegexField(source='get_content', regex='\[@[^\]]*\]\([^\) ]*\)', replace='@A User', 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': '<_id>'})

    date_created = VersionedDateTimeField(source='created', read_only=True)
    date_modified = VersionedDateTimeField(source='modified', read_only=True)
    modified = ser.BooleanField(source='edited', 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.objects.filter(target___id=obj._id).exists()

    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 ModelValidationError as err:
                    raise ValidationError(err.messages[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. 23
0
class RegistrationCreateSerializer(RegistrationSerializer):
    """
    Overrides RegistrationSerializer to add draft_registration, registration_choice, and lift_embargo fields -
    """
    def expect_cleaner_attributes(self, request):
        return StrictVersion(getattr(
            request, 'version',
            '2.0')) >= StrictVersion(CREATE_REGISTRATION_FIELD_CHANGE_VERSION)

    def __init__(self, *args, **kwargs):
        super(RegistrationCreateSerializer, self).__init__(*args, **kwargs)
        request = kwargs['context']['request']
        # required fields defined here for the different versions
        if self.expect_cleaner_attributes(request):
            self.fields['draft_registration_id'] = ser.CharField(
                write_only=True)
        else:
            self.fields['draft_registration'] = ser.CharField(write_only=True)
            self.fields['registration_choice'] = ser.ChoiceField(
                write_only=True, choices=['immediate', 'embargo'])

    # For newer versions
    embargo_end_date = VersionedDateTimeField(write_only=True,
                                              allow_null=True,
                                              default=None)
    included_node_ids = ser.ListField(write_only=True, required=False)
    # For older versions
    lift_embargo = VersionedDateTimeField(write_only=True,
                                          default=None,
                                          input_formats=['%Y-%m-%dT%H:%M:%S'])
    children = ser.ListField(write_only=True, required=False)

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

    def get_registration_choice_by_version(self, validated_data):
        """
        Old API versions should pass in "immediate" or "embargo" under `registration_choice`.
        New API versions should pass in an "embargo_end_date" if it should be embargoed, else it will be None
        """
        if self.expect_cleaner_attributes(self.context['request']):
            return 'embargo' if validated_data.get('embargo_end_date',
                                                   None) else 'immediate'
        return validated_data.get('registration_choice', 'immediate')

    def get_embargo_end_date_by_version(self, validated_data):
        """
        Old API versions should pass in "lift_embargo".
        New API versions should pass in "embargo_end_date"
        """
        if self.expect_cleaner_attributes(self.context['request']):
            return validated_data.get('embargo_end_date', None)
        return validated_data.get('lift_embargo')

    def get_children_by_version(self, validated_data):
        """
        Old API versions should pass in 'children'
        New API versions should pass in 'included_node_ids'.
        """
        if self.expect_cleaner_attributes(self.context['request']):
            return validated_data.get('included_node_ids', [])
        return validated_data.get('children', [])

    def create(self, validated_data):
        auth = get_user_auth(self.context['request'])
        draft = validated_data.pop('draft', None)
        registration_choice = self.get_registration_choice_by_version(
            validated_data)
        embargo_lifted = self.get_embargo_end_date_by_version(validated_data)

        reviewer = is_prereg_admin_not_project_admin(self.context['request'],
                                                     draft)
        children = self.get_children_by_version(validated_data)
        if children:
            # First check that all children are valid
            child_nodes = Node.objects.filter(guids___id__in=children)
            if child_nodes.count() != len(children):
                raise exceptions.ValidationError(
                    'Some child nodes could not be found.')

        # Second check that metadata doesn't have files that are not in the child nodes being registered.
        registering = children + [draft.branched_from._id]
        orphan_files = self._find_orphan_files(registering, draft)
        if orphan_files:
            orphan_files_names = [
                file_data['selectedFileName'] for file_data in orphan_files
            ]
            raise exceptions.ValidationError(
                'All files attached to this form must be registered to complete the process. '
                'The following file(s) are attached, but are not part of a component being'
                ' registered: {}'.format(', '.join(orphan_files_names)))

        try:
            # Still validating metadata, but whether `registration_responses` or `registration_metadata` were populated
            # on the draft, the other field was built and populated as well.  Both should exist.
            draft.validate_metadata(metadata=draft.registration_metadata,
                                    reviewer=reviewer,
                                    required_fields=True)
        except ValidationValueError:
            log_exception(
            )  # Probably indicates a bug on our end, so log to sentry
            # TODO: Raise an error once our JSON schemas are updated

        try:
            registration = draft.register(auth, save=True, child_ids=children)
        except NodeStateError as err:
            raise exceptions.ValidationError(err)

        if registration_choice == 'embargo':
            if not embargo_lifted:
                raise exceptions.ValidationError(
                    'lift_embargo must be specified.')
            embargo_end_date = embargo_lifted.replace(tzinfo=pytz.utc)
            try:
                registration.embargo_registration(auth.user, embargo_end_date)
            except ValidationError as err:
                raise exceptions.ValidationError(err.message)
        else:
            try:
                registration.require_approval(auth.user)
            except NodeStateError as err:
                raise exceptions.ValidationError(err)

        registration.save()
        return registration

    def _find_orphan_files(self, registering, draft):
        from website.archiver.utils import find_selected_files
        files = find_selected_files(draft.registration_schema,
                                    draft.registration_metadata)
        orphan_files = []
        for key, value in files.items():
            if 'extra' in value:
                for file_metadata in value['extra']:
                    if not self._is_attached_file_valid(
                            file_metadata, registering):
                        orphan_files.append(file_metadata)

        return orphan_files

    def _is_attached_file_valid(self, file_metadata, registering):
        """
        Validation of file information on registration_metadata.  Theoretically, the file information
        on registration_responses does not have to be valid, so we enforce their accuracy here,
        to ensure file links load properly.

        Verifying that nodeId in the file_metadata is one of the files we're registering. Verify
        that selectedFileName is the name of a file on the node.  Verify that the sha256 matches
        a version on that file.

        :param file_metadata - under "registration_metadata"
        :param registering - node ids you are registering
        :return boolean
        """

        node_id = file_metadata.get('nodeId')
        if node_id not in registering:
            return False

        node = AbstractNode.load(node_id)
        if not node:
            # node in registration_metadata doesn't exist
            return False

        specified_sha = file_metadata.get('sha256', '')

        file = node.files.filter(
            name=file_metadata.get('selectedFileName')).first()
        if not file:
            # file with this name does not exist on the node
            return False

        match = False
        for version in file.versions.all():
            if specified_sha == version.metadata.get('sha256'):
                match = True

        if not match:
            # Specified sha256 does not match a version on the specified file
            return False

        return True
Esempio n. 24
0
class RegistrationSchemaResponseSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'date_created',
        'date_modified',
        'revision_justification',
        'reviews_state',
    ])
    writeable_method_fields = frozenset([
        'revision_responses',
    ])

    non_anonymized_fields = frozenset([
        'id',
        'date_created',
        'date_modified',
        'date_submitted',
        'is_original_response',
        'links',
        'registration',
        'registration_schema',
        'revision_justification',
        'revision_responses',
        'updated_response_keys',
    ])

    id = ser.CharField(source='_id', required=True, allow_null=True)
    type = TypeField()
    date_created = VersionedDateTimeField(source='created', required=False)
    date_submitted = VersionedDateTimeField(source='submitted_timestamp',
                                            required=False)
    date_modified = VersionedDateTimeField(source='modified', required=False)
    revision_justification = ser.CharField(required=False, allow_blank=True)
    revision_responses = ser.JSONField(source='all_responses', required=False)
    updated_response_keys = ser.JSONField(required=False, read_only=True)
    reviews_state = ser.CharField(required=False)
    # Populated via annotation on relevant API views
    is_pending_current_user_approval = ser.BooleanField(required=False)
    is_original_response = ser.BooleanField(required=False)

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

    actions = RelationshipField(
        related_view='schema_responses:schema-response-action-list',
        related_view_kwargs={'schema_response_id': '<_id>'},
        read_only=True,
        required=False,
    )

    registration = RelationshipField(
        related_view='registrations:registration-detail',
        related_view_kwargs={'node_id': '<parent._id>'},
        read_only=True,
        required=False,
    )

    registration_schema = RelationshipField(
        related_view='schemas:registration-schema-detail',
        related_view_kwargs={'schema_id': '<schema._id>'},
        read_only=True,
        required=False,
    )

    initiated_by = RelationshipField(
        related_view='users:user-detail',
        related_view_kwargs={'user_id': '<initiator._id>'},
        read_only=True,
        required=False,
    )

    class Meta:
        type_ = 'schema-responses'

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

    def create(self, validated_data):
        try:
            registration_id = validated_data.pop('_id')
        except KeyError:
            raise ValidationError('payload must contain valid Registration id')

        registration = get_object_or_error(
            Registration,
            query_or_pk=registration_id,
            request=self.context['request'],
        )
        if not registration.updatable:
            raise Conflict(
                detail=
                f'Registration with guid {registration._id} cannot be updated.'
            )

        initiator = self.context['request'].user
        justification = validated_data.pop('revision_justification', '')

        latest_response = registration.schema_responses.first()
        if not latest_response:
            try:
                return SchemaResponse.create_initial_response(
                    parent=registration,
                    initiator=initiator,
                    justification=justification,
                )
            # Value Error when no schema provided
            except ValueError:
                raise ValidationError(
                    f'Resource {registration._id} must specify a schema')

        try:
            return SchemaResponse.create_from_previous_response(
                initiator=initiator,
                previous_response=latest_response,
                justification=justification,
            )
        except PreviousSchemaResponseError as exc:
            raise Conflict(detail=str(exc))

    def update(self, schema_response, validated_data):
        if schema_response.state is not ApprovalStates.IN_PROGRESS:
            raise Conflict(detail=(
                f'SchemaResponse has state `{schema_response.reviews_state}` '
                f'state must be {ApprovalStates.IN_PROGRESS.db_name}', ), )

        revision_responses = validated_data.get('revision_responses')
        justification = validated_data.get('revision_justification')

        if justification:
            schema_response.revision_justification = justification

        if revision_responses:
            try:
                schema_response.update_responses(revision_responses)
            except SchemaResponseUpdateError as exc:
                raise ValidationError(detail=str(exc))
            except SchemaResponseStateError as exc:
                # should have been handled above, but catch again just in case
                raise Conflict(detail=str(exc))

        schema_response.save()
        return schema_response
Esempio n. 25
0
class FileVersionSerializer(JSONAPISerializer):
    filterable_fields = frozenset([
        'id',
        'size',
        'identifier',
        'content_type',
    ])
    id = ser.CharField(read_only=True, source='identifier')
    size = ser.IntegerField(read_only=True,
                            help_text='The size of this file at this version')
    content_type = ser.CharField(
        read_only=True, help_text='The mime type of this file at this verison')
    date_created = VersionedDateTimeField(
        source='created',
        read_only=True,
        help_text='The date that this version was created')
    name = ser.SerializerMethodField()
    links = LinksField({
        'self': 'self_url',
        'html': 'absolute_url',
        'download': 'get_download_link',
        'render': 'get_render_link',
    })

    def get_name(self, obj):
        file = self.context['file']
        return obj.get_basefilenode_version(file).version_name

    class Meta:
        @staticmethod
        def get_type(request):
            return get_kebab_snake_case_field(request.version, 'file-versions')

    def self_url(self, obj):
        return absolute_reverse(
            'files:version-detail',
            kwargs={
                'version_id':
                obj.identifier,
                'file_id':
                self.context['view'].kwargs['file_id'],
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

    def absolute_url(self, obj):
        fobj = self.context['file']
        return furl.furl(settings.DOMAIN).set(
            path=(fobj.target._id, 'files', fobj.provider,
                  fobj.path.lstrip('/')),
            query={
                fobj.version_identifier: obj.identifier
            },  # TODO this can probably just be changed to revision or version
        ).url

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

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

    def get_render_link(self, obj):
        file = self.context['file']
        mfr_url = get_mfr_url(file.target, file.provider)
        download_url = self.get_download_link(obj)

        return get_file_render_link(mfr_url,
                                    download_url,
                                    version=obj.identifier)
Esempio n. 26
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')

    links = LinksField({})

    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>'})

    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):
        count = 0
        auth = get_user_auth(self.context['request'])
        for pointer in obj.linked_nodes.filter(is_deleted=False,
                                               type='osf.node'):
            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'):
            if pointer.can_view(auth):
                count += 1
        return 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, node, validated_data):
        """Update instance with the validated data. Requires
        the request to be in the serializer context.
        """
        assert isinstance(node,
                          AbstractNode), 'collection must be an AbstractNode'
        auth = get_user_auth(self.context['request'])

        if validated_data:
            try:
                node.update(validated_data, auth=auth)
            except ValidationError as e:
                raise InvalidModelValueError(detail=e.messages[0])
            except PermissionsError:
                raise exceptions.PermissionDenied

        return node
Esempio n. 27
0
class RegistrationSerializer(NodeSerializer):
    admin_only_editable_fields = [
        'affiliated_institutions',
        'article_doi',
        'custom_citation',
        'description',
        'is_pending_retraction',
        'is_public',
        'license',
        'license_type',
        'subjects',
        'withdrawal_justification',
        'category',
    ]

    # Remember to add new RegistrationSerializer fields to this list
    # if you don't need them to be anonymized
    non_anonymized_fields = NodeSerializer.non_anonymized_fields + [
        'archiving',
        'article_doi',
        'date_registered',
        'date_withdrawn',
        'embargo_end_date',
        'embargoed',
        'pending_embargo_approval',
        'pending_embargo_termination_approval',
        'pending_registration_approval',
        'pending_withdrawal',
        'provider',
        'registered_by',
        'registered_from',
        'registered_meta',
        'registration_responses',
        'registration_schema',
        'registration_supplement',
        'withdrawal_justification',
        'withdrawn',
    ]

    title = ser.CharField(read_only=True)
    description = ser.CharField(required=False,
                                allow_blank=True,
                                allow_null=True)
    category_choices = NodeSerializer.category_choices
    category_choices_string = NodeSerializer.category_choices_string
    category = ser.ChoiceField(required=False,
                               choices=category_choices,
                               help_text='Choices: ' + category_choices_string)
    date_modified = VersionedDateTimeField(source='last_logged',
                                           read_only=True)
    fork = HideIfWithdrawal(ser.BooleanField(read_only=True, source='is_fork'))
    collection = HideIfWithdrawal(
        ser.BooleanField(read_only=True, source='is_collection'))
    access_requests_enabled = HideIfWithdrawal(
        ser.BooleanField(read_only=True))
    node_license = HideIfWithdrawal(
        NodeLicenseSerializer(required=False, source='license'))
    tags = HideIfWithdrawal(
        ValuesListField(attr_name='name',
                        child=ser.CharField(),
                        required=False))
    article_doi = ser.CharField(required=False, allow_null=True)
    public = HideIfWithdrawal(
        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',
        ))
    current_user_permissions = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text='List of strings representing the permissions '
            'for the current user on this node.', ))

    pending_embargo_approval = HideIfWithdrawal(
        ser.BooleanField(
            read_only=True,
            source='is_pending_embargo',
            help_text=
            'The associated Embargo is awaiting approval by project admins.',
        ))
    pending_embargo_termination_approval = HideIfWithdrawal(
        ser.BooleanField(
            read_only=True,
            source='is_pending_embargo_termination',
            help_text=
            'The associated Embargo early termination is awaiting approval by project admins',
        ))
    embargoed = HideIfWithdrawal(
        ser.BooleanField(read_only=True, source='is_embargoed'))
    pending_registration_approval = HideIfWithdrawal(
        ser.BooleanField(
            source='is_pending_registration',
            read_only=True,
            help_text=
            'The associated RegistrationApproval is awaiting approval by project admins.',
        ))
    archiving = HideIfWithdrawal(ser.BooleanField(read_only=True))
    pending_withdrawal = HideIfWithdrawal(
        ser.BooleanField(
            source='is_pending_retraction',
            read_only=True,
            help_text=
            'The registration is awaiting withdrawal approval by project admins.',
        ))
    withdrawn = ser.BooleanField(
        source='is_retracted',
        read_only=True,
        help_text='The registration has been withdrawn.',
    )

    date_registered = VersionedDateTimeField(
        source='registered_date',
        read_only=True,
        help_text='Date time of registration.')
    date_withdrawn = VersionedDateTimeField(
        read_only=True,
        help_text='Date time of when this registration was retracted.')
    embargo_end_date = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text='When the embargo on this registration will be lifted.'))
    custom_citation = HideIfWithdrawal(
        ser.CharField(allow_blank=True, required=False))

    withdrawal_justification = ser.CharField(read_only=True)
    template_from = HideIfWithdrawal(
        ser.CharField(
            read_only=True,
            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.',
        ))
    registration_supplement = ser.SerializerMethodField()
    # Will be deprecated in favor of registration_responses
    registered_meta = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text=
            'A dictionary with supplemental registration questions and responses.',
        ))
    registration_responses = HideIfWithdrawal(
        ser.SerializerMethodField(
            help_text=
            'A dictionary with supplemental registration questions and responses.',
        ))
    registered_by = HideIfWithdrawal(
        RelationshipField(
            related_view='users:user-detail',
            related_view_kwargs={'user_id': '<registered_user._id>'},
        ))

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

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

    comments = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-comments',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={
                'unread': 'get_unread_comments_count',
                'count': 'get_total_comments_count',
            },
            filter={'target': '<_id>'},
        ))

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

    bibliographic_contributors = RelationshipField(
        related_view='registrations:registration-bibliographic-contributors',
        related_view_kwargs={'node_id': '<_id>'},
    )

    implicit_contributors = RelationshipField(
        related_view='registrations:registration-implicit-contributors',
        related_view_kwargs={'node_id': '<_id>'},
        help_text=
        'This feature is experimental and being tested. It may be deprecated.',
    )

    files = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-storage-providers',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_files_count'},
        ))

    wikis = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-wikis',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_wiki_page_count'},
        ))

    forked_from = HideIfWithdrawal(
        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 = HideIfWithdrawal(
        RelationshipField(
            related_view='nodes:node-detail',
            related_view_kwargs={'node_id': '<template_node._id>'},
        ))

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

    logs = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-logs',
            related_view_kwargs={'node_id': '<_id>'},
        ))

    forks = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-forks',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_forks_count'},
        ))

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

    node_links = ShowIfVersion(
        HideIfWithdrawal(
            RelationshipField(
                related_view='registrations:registration-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',
    )

    linked_by_nodes = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-linked-by-nodes',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_linked_by_nodes_count'},
        ))

    linked_by_registrations = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-linked-by-registrations',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_linked_by_registrations_count'},
        ))

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

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

    region = HideIfWithdrawal(
        RelationshipField(
            related_view='regions:region-detail',
            related_view_kwargs={'region_id': '<osfstorage_region._id>'},
            read_only=True,
        ))

    affiliated_institutions = RelationshipField(
        related_view='registrations:registration-institutions',
        related_view_kwargs={'node_id': '<_id>'},
        self_view='registrations:registration-relationships-institutions',
        self_view_kwargs={'node_id': '<_id>'},
        read_only=False,
        many=True,
        required=False,
    )

    registration_schema = RelationshipField(
        related_view='schemas:registration-schema-detail',
        related_view_kwargs={'schema_id': '<registered_schema_id>'},
    )

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

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

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

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

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

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

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

    view_only_links = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-view-only-links',
            related_view_kwargs={'node_id': '<_id>'},
            related_meta={'count': 'get_view_only_links_count'},
        ))

    citation = HideIfWithdrawal(
        RelationshipField(
            related_view='registrations:registration-citation',
            related_view_kwargs={'node_id': '<_id>'},
        ))

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

    @property
    def subjects_related_view(self):
        # Overrides TaxonomizableSerializerMixin
        return 'registrations:registration-subjects'

    @property
    def subjects_self_view(self):
        # Overrides TaxonomizableSerializerMixin
        return 'registrations:registration-relationships-subjects'

    links = LinksField({'html': 'get_absolute_html_url'})

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

    def get_registered_meta(self, obj):
        if obj.registered_meta:
            meta_values = self.anonymize_registered_meta(obj)
            try:
                return json.loads(meta_values)
            except TypeError:
                return meta_values
            except ValueError:
                return meta_values
        return None

    def get_registration_responses(self, obj):
        if obj.registration_responses:
            return self.anonymize_registration_responses(obj)
        return None

    def get_embargo_end_date(self, obj):
        if obj.embargo_end_date:
            return obj.embargo_end_date
        return None

    def get_registration_supplement(self, obj):
        if obj.registered_schema:
            schema = obj.registered_schema.first()
            if schema is None:
                return None
            return schema.name
        return None

    def get_current_user_permissions(self, obj):
        return NodeSerializer.get_current_user_permissions(self, obj)

    def get_view_only_links_count(self, obj):
        return obj.private_links.filter(is_deleted=False).count()

    def get_total_comments_count(self, obj):
        return obj.comment_set.filter(page='node', is_deleted=False).count()

    def get_files_count(self, obj):
        return obj.files_count or 0

    def anonymize_registered_meta(self, obj):
        """
        Looks at every question on every page of the schema, for any titles
        that have a contributor-input block type.  If present, deletes that question's response
        from meta_values.
        """
        cleaned_registered_meta = strip_registered_meta_comments(
            obj.registered_meta.values()[0])
        return self.anonymize_fields(obj, cleaned_registered_meta)

    def anonymize_registration_responses(self, obj):
        """
        For any questions that have a `contributor-input` block type, delete
        that question's response from registration_responses.

        We want to make sure author's names that need to be anonymized
        aren't surfaced when viewed through an anonymous VOL
        """
        return self.anonymize_fields(obj, obj.registration_responses)

    def anonymize_fields(self, obj, data):
        """
        Consolidates logic to anonymize fields with contributor information
        on both registered_meta and registration_responses
        """
        if is_anonymized(self.context['request']):
            anonymous_registration_response_keys = obj.get_contributor_registration_response_keys(
            )

            for key in anonymous_registration_response_keys:
                if key in data:
                    del data[key]

        return data

    def check_admin_perms(self, registration, user, validated_data):
        """
        While admin/write users can make both make modifications to registrations,
        most fields are restricted to admin-only edits.  You must be an admin
        contributor on the registration; you cannot have gotten your admin
        permissions through group membership.

        Add fields that need admin perms to admin_only_editable_fields
        """
        user_is_admin = registration.is_admin_contributor(user)
        for field in validated_data:
            if field in self.admin_only_editable_fields and not user_is_admin:
                raise exceptions.PermissionDenied()

    def update_registration_tags(self, registration, validated_data, auth):
        new_tags = validated_data.pop('tags', [])
        try:
            registration.update_tags(new_tags, auth=auth)
        except NodeStateError as err:
            raise Conflict(str(err))

    def retract_registration(self, registration, validated_data, user):
        is_pending_retraction = validated_data.pop('is_pending_retraction',
                                                   None)
        withdrawal_justification = validated_data.pop(
            'withdrawal_justification', None)
        if withdrawal_justification and not is_pending_retraction:
            raise exceptions.ValidationError(
                'You cannot provide a withdrawal_justification without a concurrent withdrawal request.',
            )
        if is_truthy(is_pending_retraction):
            if registration.is_pending_retraction:
                raise exceptions.ValidationError(
                    'This registration is already pending withdrawal.')
            try:
                retraction = registration.retract_registration(
                    user, withdrawal_justification, save=True)
            except NodeStateError as err:
                raise exceptions.ValidationError(str(err))
            retraction.ask(
                registration.get_active_contributors_recursive(
                    unique_users=True))
        elif is_pending_retraction is not None:
            raise exceptions.ValidationError(
                'You cannot set is_pending_withdrawal to False.')

    def update(self, registration, validated_data):
        user = self.context['request'].user
        auth = Auth(user)
        self.check_admin_perms(registration, user, validated_data)
        validated_data.pop('_id', None)

        if 'tags' in validated_data:
            self.update_registration_tags(registration, validated_data, auth)
        if 'custom_citation' in validated_data:
            registration.update_custom_citation(
                validated_data.pop('custom_citation'), auth)
        if 'license_type' in validated_data or 'license' in validated_data:
            license_details = get_license_details(registration, validated_data)
            validated_data['node_license'] = license_details
            validated_data.pop('license_type', None)
            validated_data.pop('license', None)
        if 'affiliated_institutions' in validated_data:
            institutions_list = validated_data.pop('affiliated_institutions')
            new_institutions = [{
                '_id': institution
            } for institution in institutions_list]
            update_institutions(registration, new_institutions, user)
            registration.save()
        if 'subjects' in validated_data:
            subjects = validated_data.pop('subjects', None)
            self.update_subjects(registration, subjects, auth)
        if 'withdrawal_justification' in validated_data or 'is_pending_retraction' in validated_data:
            self.retract_registration(registration, validated_data, user)
        if 'is_public' in validated_data:
            if validated_data.get('is_public') is False:
                raise exceptions.ValidationError(
                    'Registrations can only be turned from private to public.')

        try:
            registration.update(validated_data, auth=auth)
        except ValidationError as e:
            raise InvalidModelValueError(detail=e.messages[0])
        except NodeUpdateError as err:
            raise exceptions.ValidationError(err.reason)
        except NodeStateError as err:
            raise exceptions.ValidationError(str(err))

        return registration

    class Meta:
        type_ = 'registrations'
Esempio n. 28
0
class WikiVersionSerializer(JSONAPISerializer):
    id = ser.CharField(read_only=True, source='identifier')
    type = TypeField()
    size = ser.SerializerMethodField()
    content_type = ser.SerializerMethodField()
    date_created = VersionedDateTimeField(
        source='created',
        read_only=True,
        help_text='The date that this version was created')

    wiki_page = RelationshipField(
        related_view='wikis:wiki-detail',
        related_view_kwargs={'wiki_id': '<wiki_page._id>'},
    )

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

    links = LinksField({
        'self': 'self_url',
        'download': 'get_wiki_content',
    })

    def self_url(self, obj):
        return absolute_reverse(
            'wikis:wiki-version-detail',
            kwargs={
                'version_id':
                obj.identifier,
                'wiki_id':
                obj.wiki_page._id,
                'version':
                self.context['request'].parser_context['kwargs']['version'],
            },
        )

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

    def get_size(self, obj):
        # The size of this wiki at this version
        return sys.getsizeof(obj.content)

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

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

    def create(self, validated_data):
        auth = Auth(self.context['request'].user)
        wiki_page = self.context['view'].get_wiki()
        content = validated_data.get('content', '')
        new_version = wiki_page.update(auth.user, content)
        return new_version

    class Meta:
        type_ = 'wiki-versions'
Esempio n. 29
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. 30
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