class NodeSerializer(JSONAPISerializer): # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this # instance filterable_fields = frozenset([ 'id', 'title', 'description', 'public', 'tags', 'category', 'date_created', 'date_modified', 'root', 'parent', 'contributors', 'preprint' ]) non_anonymized_fields = [ 'id', 'title', 'description', 'category', 'date_created', 'date_modified', 'registration', 'tags', 'public', 'license', 'links', 'children', 'comments', 'contributors', 'files', 'node_links', 'parent', 'root', 'logs', 'wikis' ] id = IDField(source='_id', read_only=True) type = TypeField() category_choices = settings.NODE_CATEGORY_MAP.items() category_choices_string = ', '.join( ["'{}'".format(choice[0]) for choice in category_choices]) title = ser.CharField(required=True) description = ser.CharField(required=False, allow_blank=True, allow_null=True) category = ser.ChoiceField(choices=category_choices, help_text='Choices: ' + category_choices_string) date_created = ser.DateTimeField(read_only=True) date_modified = ser.DateTimeField(read_only=True) registration = ser.BooleanField(read_only=True, source='is_registration') preprint = ser.BooleanField(read_only=True, source='is_preprint') fork = ser.BooleanField(read_only=True, source='is_fork') collection = ser.BooleanField(read_only=True, source='is_collection') tags = JSONAPIListField(child=NodeTagField(), required=False) node_license = NodeLicenseSerializer(read_only=True, required=False) template_from = ser.CharField( required=False, allow_blank=False, allow_null=False, help_text= 'Specify a node id for a node you would like to use as a template for the ' 'new node. Templating is like forking, except that you do not copy the ' 'files, only the project structure. Some information is changed on the top ' 'level project by submitting the appropriate fields in the request body, ' 'and some information will not change. By default, the description will ' 'be cleared and the project will be made private.') current_user_can_comment = ser.SerializerMethodField( help_text='Whether the current user is allowed to post comments') current_user_permissions = ser.SerializerMethodField( help_text='List of strings representing the permissions ' 'for the current user on this node.') # Public is only write-able by admins--see update method public = ser.BooleanField( source='is_public', required=False, help_text='Nodes that are made public will give read-only access ' 'to everyone. Private nodes require explicit read ' 'permission. Write and admin access are the same for ' 'public and private nodes. Administrators on a parent ' 'node have implicit read permissions for all child nodes') links = LinksField({'html': 'get_absolute_html_url'}) # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins license = RelationshipField( related_view='licenses:license-detail', related_view_kwargs={'license_id': '<node_license.node_license._id>'}, ) children = RelationshipField( related_view='nodes:node-children', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_node_count'}, ) comments = RelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<pk>'}, related_meta={'unread': 'get_unread_comments_count'}, filter={'target': '<pk>'}) contributors = RelationshipField( related_view='nodes:node-contributors', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_contrib_count'}, ) files = RelationshipField(related_view='nodes:node-providers', related_view_kwargs={'node_id': '<pk>'}) wikis = RelationshipField(related_view='nodes:node-wikis', related_view_kwargs={'node_id': '<pk>'}) forked_from = RelationshipField( related_view=lambda n: 'registrations:registration-detail' if getattr(n, 'is_registration', False) else 'nodes:node-detail', related_view_kwargs={'node_id': '<forked_from_id>'}) template_node = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<template_node._id>'}) forks = RelationshipField(related_view='nodes:node-forks', related_view_kwargs={'node_id': '<pk>'}) node_links = RelationshipField( related_view='nodes:node-pointers', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_pointers_count'}, ) parent = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<parent_node._id>'}, filter_key='parent_node') draft_registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-draft-registrations', related_view_kwargs={'node_id': '<pk>'})) registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-registrations', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_registration_count'})) affiliated_institutions = RelationshipField( related_view='nodes:node-institutions', related_view_kwargs={'node_id': '<pk>'}, self_view='nodes:node-relationships-institutions', self_view_kwargs={'node_id': '<pk>'}) root = RelationshipField(related_view='nodes:node-detail', related_view_kwargs={'node_id': '<root._id>'}) logs = RelationshipField(related_view='nodes:node-logs', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_logs_count'}) linked_nodes = RelationshipField( related_view='nodes:linked-nodes', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_node_links_count'}, self_view='nodes:node-pointer-relationship', self_view_kwargs={'node_id': '<pk>'}, self_meta={'count': 'get_node_links_count'}) view_only_links = RelationshipField( related_view='nodes:node-view-only-links', related_view_kwargs={'node_id': '<pk>'}, ) def get_current_user_permissions(self, obj): user = self.context['request'].user if user.is_anonymous(): return ['read'] permissions = obj.get_permissions(user=user) if not permissions: permissions = ['read'] return permissions def get_current_user_can_comment(self, obj): user = self.context['request'].user auth = Auth(user if not user.is_anonymous() else None) return obj.can_comment(auth) class Meta: type_ = 'nodes' def get_absolute_url(self, obj): return obj.get_absolute_url() # TODO: See if we can get the count filters into the filter rather than the serializer. def get_logs_count(self, obj): return len(obj.logs) def get_node_count(self, obj): auth = get_user_auth(self.context['request']) nodes = [ node for node in obj.nodes if node.can_view(auth) and node.primary and not node.is_deleted ] return len(nodes) def get_contrib_count(self, obj): return len(obj.contributors) def get_registration_count(self, obj): auth = get_user_auth(self.context['request']) registrations = [ node for node in obj.registrations_all if node.can_view(auth) ] return len(registrations) def get_pointers_count(self, obj): return len(obj.nodes_pointer) def get_node_links_count(self, obj): count = 0 auth = get_user_auth(self.context['request']) for pointer in obj.nodes_pointer: if not pointer.node.is_deleted and not pointer.node.is_collection and pointer.node.can_view( auth): count += 1 return count def get_unread_comments_count(self, obj): user = get_user_auth(self.context['request']).user node_comments = Comment.find_n_unread(user=user, node=obj, page='node') return {'node': node_comments} def create(self, validated_data): request = self.context['request'] user = request.user if 'template_from' in validated_data: template_from = validated_data.pop('template_from') template_node = Node.load(key=template_from) if template_node is None: raise exceptions.NotFound if not template_node.has_permission( user, 'read', check_parent=False): raise exceptions.PermissionDenied validated_data.pop('creator') changed_data = {template_from: validated_data} node = template_node.use_as_template(auth=get_user_auth(request), changes=changed_data) else: node = Node(**validated_data) try: node.save() except ValidationValueError as e: raise InvalidModelValueError(detail=e.message) if is_truthy(request.GET.get('inherit_contributors') ) and validated_data['parent'].has_permission( user, 'write'): auth = get_user_auth(request) parent = validated_data['parent'] contributors = [] for contributor in parent.contributors: if contributor is not user: contributors.append({ 'user': contributor, 'permissions': parent.get_permissions(contributor), 'visible': parent.get_visible(contributor) }) node.add_contributors(contributors, auth=auth, log=True, save=True) return node def update(self, node, validated_data): """Update instance with the validated data. Requires the request to be in the serializer context. """ assert isinstance(node, Node), 'node must be a Node' auth = get_user_auth(self.context['request']) old_tags = set([tag._id for tag in node.tags]) if 'tags' in validated_data: current_tags = set(validated_data.pop('tags', [])) elif self.partial: current_tags = set(old_tags) else: current_tags = set() for new_tag in (current_tags - old_tags): node.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): node.remove_tag(deleted_tag, auth=auth) if validated_data: try: node.update(validated_data, auth=auth) except ValidationValueError as e: raise InvalidModelValueError(detail=e.message) except PermissionsError: raise exceptions.PermissionDenied except NodeUpdateError as e: raise exceptions.ValidationError(detail=e.reason) except NodeStateError as e: raise InvalidModelValueError(detail=e.message) return node
class NodeSerializer(JSONAPISerializer): # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this # instance filterable_fields = frozenset([ 'title', 'description', 'public', 'tags', 'category', 'date_created', 'date_modified', 'registration' ]) id = IDField(source='_id', read_only=True) type = TypeField() category_choices = Node.CATEGORY_MAP.keys() category_choices_string = ', '.join(["'{}'".format(choice) for choice in category_choices]) title = ser.CharField(required=True) description = ser.CharField(required=False, allow_blank=True, allow_null=True) category = ser.ChoiceField(choices=category_choices, help_text="Choices: " + category_choices_string) date_created = ser.DateTimeField(read_only=True) date_modified = ser.DateTimeField(read_only=True) registration = ser.BooleanField(read_only=True, source='is_registration') fork = ser.BooleanField(read_only=True, source='is_fork') collection = DevOnly(ser.BooleanField(read_only=True, source='is_folder')) dashboard = ser.BooleanField(read_only=True, source='is_dashboard') tags = JSONAPIListField(child=NodeTagField(), required=False) # Public is only write-able by admins--see update method public = ser.BooleanField(source='is_public', required=False, help_text='Nodes that are made public will give read-only access ' 'to everyone. Private nodes require explicit read ' 'permission. Write and admin access are the same for ' 'public and private nodes. Administrators on a parent ' 'node have implicit read permissions for all child nodes') links = LinksField({'html': 'get_absolute_url'}) # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins children = RelationshipField( related_view='nodes:node-children', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_node_count'}, ) comments = RelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<pk>'}, related_meta={'unread': 'get_unread_comments_count'}) contributors = RelationshipField( related_view='nodes:node-contributors', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_contrib_count'}, ) files = RelationshipField( related_view='nodes:node-providers', related_view_kwargs={'node_id': '<pk>'} ) forked_from = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<forked_from_id>'} ) node_links = DevOnly(RelationshipField( related_view='nodes:node-pointers', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_pointers_count'}, )) parent = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<parent_id>'} ) registrations = DevOnly(HideIfRegistration(RelationshipField( related_view='nodes:node-registrations', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_registration_count'} ))) logs = RelationshipField( related_view='nodes:node-logs', related_view_kwargs={'node_id': '<pk>'}, ) class Meta: type_ = 'nodes' def get_absolute_url(self, obj): return obj.absolute_url # TODO: See if we can get the count filters into the filter rather than the serializer. def get_user_auth(self, request): user = request.user if user.is_anonymous(): auth = Auth(None) else: auth = Auth(user) return auth def get_node_count(self, obj): auth = self.get_user_auth(self.context['request']) nodes = [node for node in obj.nodes if node.can_view(auth) and node.primary and not node.is_deleted] return len(nodes) def get_contrib_count(self, obj): return len(obj.contributors) def get_registration_count(self, obj): auth = self.get_user_auth(self.context['request']) registrations = [node for node in obj.node__registrations if node.can_view(auth)] return len(registrations) def get_pointers_count(self, obj): return len(obj.nodes_pointer) def get_unread_comments_count(self, obj): auth = self.get_user_auth(self.context['request']) user = auth.user return Comment.find_unread(user=user, node=obj) def create(self, validated_data): node = Node(**validated_data) try: node.save() except ValidationValueError as e: raise InvalidModelValueError(detail=e.message) return node def update(self, node, validated_data): """Update instance with the validated data. Requires the request to be in the serializer context. """ assert isinstance(node, Node), 'node must be a Node' auth = self.get_user_auth(self.context['request']) old_tags = set([tag._id for tag in node.tags]) if 'tags' in validated_data: current_tags = set(validated_data.get('tags')) del validated_data['tags'] elif self.partial: current_tags = set(old_tags) else: current_tags = set() for new_tag in (current_tags - old_tags): node.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): node.remove_tag(deleted_tag, auth=auth) if validated_data: try: node.update(validated_data, auth=auth) except ValidationValueError as e: raise InvalidModelValueError(detail=e.message) except PermissionsError: raise exceptions.PermissionDenied return node
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 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 NodeSerializer(JSONAPISerializer): # TODO: If we have to redo this implementation in any of the other serializers, subclass ChoiceField and make it # handle blank choices properly. Currently DRF ChoiceFields ignore blank options, which is incorrect in this # instance filterable_fields = frozenset([ 'id', 'title', 'description', 'public', 'tags', 'category', 'date_created', 'date_modified', 'root', 'parent', 'contributors', 'preprint' ]) non_anonymized_fields = [ 'id', 'title', 'description', 'category', 'date_created', 'date_modified', 'registration', 'tags', 'public', 'license', 'links', 'children', 'comments', 'contributors', 'files', 'node_links', 'parent', 'root', 'logs', 'wikis' ] id = IDField(source='_id', read_only=True) type = TypeField() category_choices = settings.NODE_CATEGORY_MAP.items() category_choices_string = ', '.join( ["'{}'".format(choice[0]) for choice in category_choices]) title = ser.CharField(required=True) description = ser.CharField(required=False, allow_blank=True, allow_null=True) category = ser.ChoiceField(choices=category_choices, help_text='Choices: ' + category_choices_string) date_created = DateByVersion(source='created', read_only=True) date_modified = DateByVersion(source='last_logged', read_only=True) registration = ser.BooleanField(read_only=True, source='is_registration') preprint = ser.BooleanField(read_only=True, source='is_preprint') fork = ser.BooleanField(read_only=True, source='is_fork') collection = ser.BooleanField(read_only=True, source='is_collection') tags = JSONAPIListField(child=NodeTagField(), required=False) node_license = NodeLicenseSerializer(required=False, source='license') template_from = ser.CharField( required=False, allow_blank=False, allow_null=False, help_text= 'Specify a node id for a node you would like to use as a template for the ' 'new node. Templating is like forking, except that you do not copy the ' 'files, only the project structure. Some information is changed on the top ' 'level project by submitting the appropriate fields in the request body, ' 'and some information will not change. By default, the description will ' 'be cleared and the project will be made private.') current_user_can_comment = ser.SerializerMethodField( help_text='Whether the current user is allowed to post comments') current_user_permissions = ser.SerializerMethodField( help_text='List of strings representing the permissions ' 'for the current user on this node.') # Public is only write-able by admins--see update method public = ser.BooleanField( source='is_public', required=False, help_text='Nodes that are made public will give read-only access ' 'to everyone. Private nodes require explicit read ' 'permission. Write and admin access are the same for ' 'public and private nodes. Administrators on a parent ' 'node have implicit read permissions for all child nodes') links = LinksField({'html': 'get_absolute_html_url'}) # TODO: When we have osf_permissions.ADMIN permissions, make this writable for admins license = NodeLicenseRelationshipField( related_view='licenses:license-detail', related_view_kwargs={'license_id': '<license.node_license._id>'}, read_only=False) children = RelationshipField( related_view='nodes:node-children', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_node_count'}, ) comments = RelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<_id>'}, related_meta={'unread': 'get_unread_comments_count'}, filter={'target': '<_id>'}) contributors = RelationshipField( related_view='nodes:node-contributors', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_contrib_count'}, ) files = RelationshipField(related_view='nodes:node-providers', related_view_kwargs={'node_id': '<_id>'}) wikis = RelationshipField(related_view='nodes:node-wikis', related_view_kwargs={'node_id': '<_id>'}) forked_from = RelationshipField( related_view=lambda n: 'registrations:registration-detail' if getattr(n, 'is_registration', False) else 'nodes:node-detail', related_view_kwargs={'node_id': '<forked_from_guid>'}) template_node = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<template_node._id>'}) forks = RelationshipField(related_view='nodes:node-forks', related_view_kwargs={'node_id': '<_id>'}) node_links = ShowIfVersion(RelationshipField( related_view='nodes:node-pointers', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_pointers_count'}, help_text= 'This feature is deprecated as of version 2.1. Use linked_nodes instead.' ), min_version='2.0', max_version='2.0') parent = RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<parent_node._id>'}, filter_key='parent_node') identifiers = RelationshipField(related_view='nodes:identifier-list', related_view_kwargs={'node_id': '<_id>'}) draft_registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-draft-registrations', related_view_kwargs={'node_id': '<_id>'})) registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-registrations', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_registration_count'})) affiliated_institutions = RelationshipField( related_view='nodes:node-institutions', related_view_kwargs={'node_id': '<_id>'}, self_view='nodes:node-relationships-institutions', self_view_kwargs={'node_id': '<_id>'}) root = RelationshipField(related_view='nodes:node-detail', related_view_kwargs={'node_id': '<root._id>'}) logs = RelationshipField(related_view='nodes:node-logs', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_logs_count'}) linked_nodes = RelationshipField( related_view='nodes:linked-nodes', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_node_links_count'}, self_view='nodes:node-pointer-relationship', self_view_kwargs={'node_id': '<_id>'}, self_meta={'count': 'get_node_links_count'}) linked_registrations = RelationshipField( related_view='nodes:linked-registrations', related_view_kwargs={'node_id': '<_id>'}, related_meta={'count': 'get_registration_links_count'}, self_view='nodes:node-registration-pointer-relationship', self_view_kwargs={'node_id': '<_id>'}, self_meta={'count': 'get_node_links_count'}) view_only_links = RelationshipField( related_view='nodes:node-view-only-links', related_view_kwargs={'node_id': '<_id>'}, ) citation = RelationshipField(related_view='nodes:node-citation', related_view_kwargs={'node_id': '<_id>'}) preprints = HideIfRegistration( RelationshipField(related_view='nodes:node-preprints', related_view_kwargs={'node_id': '<_id>'})) def get_current_user_permissions(self, obj): user = self.context['request'].user if user.is_anonymous: return ['read'] permissions = obj.get_permissions(user=user) if not permissions: permissions = ['read'] return permissions def get_current_user_can_comment(self, obj): user = self.context['request'].user auth = Auth(user if not user.is_anonymous else None) return obj.can_comment(auth) class Meta: type_ = 'nodes' def get_absolute_url(self, obj): return obj.get_absolute_url() # TODO: See if we can get the count filters into the filter rather than the serializer. def get_logs_count(self, obj): return obj.logs.count() def get_node_count(self, obj): auth = get_user_auth(self.context['request']) user_id = getattr(auth.user, 'id', None) with connection.cursor() as cursor: cursor.execute( ''' WITH RECURSIVE parents AS ( SELECT parent_id, child_id FROM osf_noderelation WHERE child_id = %s AND is_node_link IS FALSE UNION ALL SELECT osf_noderelation.parent_id, parents.parent_id AS child_id FROM parents JOIN osf_noderelation ON parents.PARENT_ID = osf_noderelation.child_id WHERE osf_noderelation.is_node_link IS FALSE ), has_admin AS (SELECT * FROM osf_contributor WHERE (node_id IN (SELECT parent_id FROM parents) OR node_id = %s) AND user_id = %s AND admin IS TRUE LIMIT 1) SELECT DISTINCT COUNT(child_id) FROM osf_noderelation JOIN osf_abstractnode ON osf_noderelation.child_id = osf_abstractnode.id JOIN osf_contributor ON osf_abstractnode.id = osf_contributor.node_id LEFT JOIN osf_privatelink_nodes ON osf_abstractnode.id = osf_privatelink_nodes.abstractnode_id LEFT JOIN osf_privatelink ON osf_privatelink_nodes.privatelink_id = osf_privatelink.id WHERE parent_id = %s AND is_node_link IS FALSE AND osf_abstractnode.is_deleted IS FALSE AND ( osf_abstractnode.is_public OR (TRUE IN (SELECT TRUE FROM has_admin)) OR (osf_contributor.user_id = %s AND osf_contributor.read IS TRUE) OR (osf_privatelink.key = %s AND osf_privatelink.is_deleted = FALSE) ); ''', [obj.id, obj.id, user_id, obj.id, user_id, auth.private_key]) return int(cursor.fetchone()[0]) def get_contrib_count(self, obj): return len(obj.contributors) def get_registration_count(self, obj): auth = get_user_auth(self.context['request']) registrations = [ node for node in obj.registrations_all if node.can_view(auth) ] return len(registrations) def get_pointers_count(self, obj): return obj.linked_nodes.count() def get_node_links_count(self, obj): count = 0 auth = get_user_auth(self.context['request']) for pointer in obj.linked_nodes.filter(is_deleted=False).exclude( type='osf.collection').exclude(type='osf.registration'): if pointer.can_view(auth): count += 1 return count def get_registration_links_count(self, obj): count = 0 auth = get_user_auth(self.context['request']) for pointer in obj.linked_nodes.filter( is_deleted=False, type='osf.registration').exclude(type='osf.collection'): if pointer.can_view(auth): count += 1 return count def get_unread_comments_count(self, obj): user = get_user_auth(self.context['request']).user node_comments = Comment.find_n_unread(user=user, node=obj, page='node') return {'node': node_comments} def create(self, validated_data): request = self.context['request'] user = request.user Node = apps.get_model('osf.Node') tag_instances = [] if 'tags' in validated_data: tags = validated_data.pop('tags') for tag in tags: tag_instance, created = Tag.objects.get_or_create( name=tag, defaults=dict(system=False)) tag_instances.append(tag_instance) if 'template_from' in validated_data: template_from = validated_data.pop('template_from') template_node = Node.load(template_from) if template_node is None: raise exceptions.NotFound if not template_node.has_permission( user, 'read', check_parent=False): raise exceptions.PermissionDenied validated_data.pop('creator') changed_data = {template_from: validated_data} node = template_node.use_as_template(auth=get_user_auth(request), changes=changed_data) else: node = Node(**validated_data) try: node.save() except ValidationError as e: raise InvalidModelValueError(detail=e.messages[0]) if len(tag_instances): for tag in tag_instances: node.tags.add(tag) if is_truthy(request.GET.get('inherit_contributors') ) and validated_data['parent'].has_permission( user, 'write'): auth = get_user_auth(request) parent = validated_data['parent'] contributors = [] for contributor in parent.contributor_set.exclude(user=user): contributors.append({ 'user': contributor.user, 'permissions': parent.get_permissions(contributor.user), 'visible': contributor.visible }) if not contributor.user.is_registered: node.add_unregistered_contributor( fullname=contributor.user.fullname, email=contributor.user.email, auth=auth, permissions=parent.get_permissions(contributor.user), existing_user=contributor.user) node.add_contributors(contributors, auth=auth, log=True, save=True) return node def update(self, node, validated_data): """Update instance with the validated data. Requires the request to be in the serializer context. """ assert isinstance(node, AbstractNode), 'node must be a Node' auth = get_user_auth(self.context['request']) # Update tags if 'tags' in validated_data: new_tags = set(validated_data.pop('tags', [])) node.update_tags(new_tags, auth=auth) if validated_data: if 'license_type' in validated_data or 'license' in validated_data: license_details = get_license_details(node, validated_data) validated_data['node_license'] = license_details try: node.update(validated_data, auth=auth) except ValidationError as e: raise InvalidModelValueError(detail=e.message) except PermissionsError: raise exceptions.PermissionDenied except NodeUpdateError as e: raise exceptions.ValidationError(detail=e.reason) except NodeStateError as e: raise InvalidModelValueError(detail=e.message) return node
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 RegistrationSerializer(NodeSerializer): pending_embargo_approval = HideIfRetraction( ser.BooleanField( read_only=True, source='is_pending_embargo', help_text= 'The associated Embargo is awaiting approval by project admins.')) pending_registration_approval = HideIfRetraction( ser.BooleanField( source='is_pending_registration', read_only=True, help_text= 'The associated RegistrationApproval is awaiting approval by project admins.' )) pending_withdrawal = HideIfRetraction( 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 = HideIfRetraction( ser.SerializerMethodField( help_text='When the embargo on this registration will be lifted.')) withdrawal_justification = ser.CharField(source='retraction.justification', read_only=True) registration_supplement = ser.SerializerMethodField() registered_meta = HideIfRetraction( ser.SerializerMethodField( help_text= 'A dictionary with supplemental registration questions and responses.' )) registered_by = HideIfRetraction( RelationshipField( related_view='users:user-detail', related_view_kwargs={'user_id': '<registered_user_id>'})) registered_from = HideIfRetraction( RelationshipField( related_view='nodes:node-detail', related_view_kwargs={'node_id': '<registered_from_id>'})) children = HideIfRetraction( RelationshipField( related_view='registrations:registration-children', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_node_count'}, )) comments = HideIfRetraction( 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 = HideIfRetraction( RelationshipField(related_view='registrations:registration-providers', related_view_kwargs={'node_id': '<pk>'})) forked_from = HideIfRetraction( RelationshipField(related_view='nodes:node-detail', related_view_kwargs={'node_id': '<forked_from_id>'})) node_links = HideIfRetraction( RelationshipField(related_view='registrations:registration-pointers', related_view_kwargs={'node_id': '<pk>'}, related_meta={'count': 'get_pointers_count'})) parent = HideIfRetraction( RelationshipField(related_view='registrations:registration-detail', related_view_kwargs={'node_id': '<parent_node._id>'}, filter_key='parent_node')) logs = HideIfRetraction( RelationshipField( related_view='registrations:registration-logs', related_view_kwargs={'node_id': '<pk>'}, )) root = HideIfRetraction( RelationshipField(related_view='registrations:registration-detail', related_view_kwargs={'node_id': '<root._id>'})) primary_institution = RelationshipField( related_view='registrations:registration-institution-detail', related_view_kwargs={'node_id': '<pk>'}) registrations = HideIfRegistration( RelationshipField(related_view='nodes:node-registrations', 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 update(self, *args, **kwargs): raise exceptions.APIException('Registrations cannot be modified.') class Meta: type_ = 'registrations'