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