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( JSONAPIListField(child=NodeTagField(), 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: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 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 = DateByVersion(read_only=True) date_modified = DateByVersion(read_only=True) date_published = DateByVersion(read_only=True) original_publication_date = DateByVersion(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') contributors = RelationshipField( related_view='nodes:node-contributors', related_view_kwargs={'node_id': '<node._id>'}, ) reviews_state = ser.CharField(read_only=True, max_length=15) date_last_transitioned = DateByVersion(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) actions = RelationshipField(related_view='preprints:preprint-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') return 'https://dx.doi.org/{}'.format( doi_identifier.value) if doi_identifier 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( 'actions:create-action', kwargs={ 'version': self.context['request'].parser_context['kwargs'] ['version'] }))) 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 RegistrationSerializer(NodeSerializer): category_choices = NodeSerializer.category_choices category_choices_string = NodeSerializer.category_choices_string category = HideIfWithdrawal( ser.ChoiceField(choices=category_choices, help_text="Choices: " + category_choices_string)) date_modified = HideIfWithdrawal(ser.DateTimeField(read_only=True)) fork = HideIfWithdrawal(ser.BooleanField(read_only=True, source='is_fork')) collection = HideIfWithdrawal( ser.BooleanField(read_only=True, source='is_collection')) node_license = HideIfWithdrawal(NodeLicenseSerializer()) tags = HideIfWithdrawal( JSONAPIListField(child=NodeTagField(), 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 = ser.DateTimeField(source='registered_date', read_only=True, help_text='Date time of registration.') 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( required=False, allow_blank=False, allow_null=False, help_text= 'Specify a node id for a node you would like to use as a template for the ' 'new node. Templating is like forking, except that you do not copy the ' 'files, only the project structure. Some information is changed on the top ' 'level project by submitting the appropriate fields in the request body, ' 'and some information will not change. By default, the description will ' 'be cleared and the project will be made private.')) 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': '<pk>'}, related_meta={'count': 'get_node_count'}, )) comments = HideIfWithdrawal( RelationshipField(related_view='registrations:registration-comments', related_view_kwargs={'node_id': '<pk>'}, related_meta={'unread': 'get_unread_comments_count'})) contributors = RelationshipField( related_view='registrations:registration-contributors', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_contrib_count'}) files = HideIfWithdrawal( RelationshipField(related_view='registrations:registration-providers', related_view_kwargs={'node_id': '<pk>'})) wikis = HideIfWithdrawal( RelationshipField( related_view='registrations:registration-wikis', related_view_kwargs={'node_id': '<pk>'}, )) 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>'})) license = HideIfWithdrawal( RelationshipField( related_view='licenses:license-detail', related_view_kwargs={ 'license_id': '<node_license.node_license._id>' }, )) forks = HideIfWithdrawal( RelationshipField(related_view='registrations:registration-forks', related_view_kwargs={'node_id': '<pk>'})) node_links = HideIfWithdrawal( RelationshipField(related_view='registrations:registration-pointers', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_pointers_count'})) parent = HideIfWithdrawal( RelationshipField(related_view='registrations:registration-detail', related_view_kwargs={'node_id': '<parent_node._id>'}, filter_key='parent_node')) logs = HideIfWithdrawal( RelationshipField( related_view='registrations:registration-logs', related_view_kwargs={'node_id': '<pk>'}, )) 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': '<pk>'})) registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-registrations', related_view_kwargs={'node_id': '<pk>'})) identifiers = HideIfWithdrawal( RelationshipField(related_view='registrations:identifier-list', related_view_kwargs={'node_id': '<pk>'})) # TODO: Finish me # TODO: Override create? 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}) def get_absolute_url(self, obj): return self.get_registration_url(obj) 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[0] 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, *args, **kwargs): raise exceptions.APIException('Registrations cannot be modified.') class Meta: type_ = 'registrations'
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', 'title', 'tags', 'date_created', 'date_modified', 'contributors', 'subjects', 'doi' ]) title = ser.CharField(required=False) subjects = JSONAPIListField(child=TaxonomyField(), required=False, source='preprint_subjects') provider = ser.CharField(source='preprint_provider', required=False) date_created = ser.DateTimeField(read_only=True, source='preprint_created') date_modified = ser.DateTimeField(read_only=True) id = IDField(source='_id', required=False) abstract = ser.CharField(source='description', required=False) tags = JSONAPIListField(child=NodeTagField(), required=False) doi = ser.CharField(source='preprint_doi', required=False) primary_file = PrimaryFileRelationshipField( related_view='files:file-detail', related_view_kwargs={'file_id': '<preprint_file._id>'}, lookup_url_kwarg='file_id', read_only=False) files = RelationshipField(related_view='nodes:node-providers', related_view_kwargs={'node_id': '<pk>'}) providers = RelationshipField( related_view='preprints:preprint-preprint_providers', related_view_kwargs={'node_id': '<pk>'}, self_view='preprints:preprint-relationships-preprint_providers', self_view_kwargs={'node_id': '<pk>'}) links = LinksField({ 'self': 'get_preprint_url', 'html': 'get_absolute_html_url', 'doi': 'get_doi_url' }) contributors = RelationshipField( related_view='nodes:node-contributors', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_contrib_count'}, ) class Meta: type_ = 'preprints' def get_preprint_url(self, obj): return absolute_reverse('preprints:preprint-detail', kwargs={'node_id': obj._id}) def get_absolute_url(self, obj): return self.get_preprint_url(obj) def get_doi_url(self, obj): return 'https://dx.doi.org/{}'.format( obj.preprint_doi) if obj.preprint_doi else None def create(self, validated_data): node = Node.load(validated_data.pop('_id', None)) if not node: raise exceptions.NotFound('Unable to find Node with specified id.') auth = get_user_auth(self.context['request']) if not node.has_permission(auth.user, permissions.ADMIN): raise exceptions.PermissionDenied if node.is_preprint: raise Conflict( 'This node already stored as a preprint, use the update method instead.' ) primary_file = validated_data.pop('primary_file', None) if not primary_file: raise exceptions.ValidationError( detail='You must specify a primary_file to create a preprint.') self.set_node_field(node.set_preprint_file, primary_file, auth) subjects = validated_data.pop('preprint_subjects', None) if not subjects: raise exceptions.ValidationError( detail= 'You must specify at least one subject to create a preprint.') self.set_node_field(node.set_preprint_subjects, subjects, auth) tags = validated_data.pop('tags', None) if tags: for tag in tags: node.add_tag(tag, auth, save=False, log=False) for key, value in validated_data.iteritems(): setattr(node, key, value) try: node.save() except ValidationValueError as e: raise exceptions.ValidationError(detail=e.message) # Send preprint confirmation email signal to new authors on preprint! for author in node.contributors: if author != auth.user: project_signals.contributor_added.send( node, contributor=author, auth=auth, email_template='preprint') return node def update(self, node, validated_data): from website.models import Node assert isinstance(node, Node), 'You must specify a valid node to be updated.' auth = get_user_auth(self.context['request']) primary_file = validated_data.pop('primary_file', None) if primary_file: self.set_node_field(node.set_preprint_file, primary_file, auth) subjects = validated_data.pop('preprint_subjects', None) if subjects: self.set_node_field(node.set_preprint_subjects, subjects, auth) old_tags = set([tag._id for tag in node.tags]) if 'tags' in validated_data: current_tags = set(validated_data.pop('tags', [])) elif self.partial: current_tags = set(old_tags) else: current_tags = set() for new_tag in (current_tags - old_tags): node.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): node.remove_tag(deleted_tag, auth=auth) for key, value in validated_data.iteritems(): setattr(node, key, value) try: node.save() except ValidationValueError as e: raise exceptions.ValidationError(detail=e.message) return node def set_node_field(self, func, val, auth): try: func(val, auth, save=False) except PermissionsError: raise exceptions.PermissionDenied( 'Not authorized to update this node.') except ValueError as e: raise exceptions.ValidationError(detail=e.message)