class WikiSerializer(JSONAPISerializer): filterable_fields = frozenset(['name', 'date_modified']) id = IDField(source='_id', read_only=True) type = TypeField() name = ser.CharField(source='page_name') kind = ser.SerializerMethodField() size = ser.SerializerMethodField() path = ser.SerializerMethodField() materialized_path = ser.SerializerMethodField(method_name='get_path') date_modified = ser.DateTimeField(source='date') content_type = ser.SerializerMethodField() extra = ser.SerializerMethodField( help_text='Additional metadata about this wiki') user = RelationshipField(related_view='users:user-detail', related_view_kwargs={'user_id': '<user._id>'}) node = RelationshipField(related_view='nodes:node-detail', related_view_kwargs={'node_id': '<node._id>'}) comments = RelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<node._id>'}, related_meta={'unread': 'get_unread_comments_count'}, filter={'target': '<pk>'}) # LinksField.to_representation adds link to "self" links = LinksField({ 'info': Link('wikis:wiki-detail', kwargs={'wiki_id': '<_id>'}), 'download': 'get_wiki_content' }) class Meta: type_ = 'wikis' def get_absolute_url(self, obj): return obj.get_absolute_url() def get_path(self, obj): return '/{}'.format(obj) def get_kind(self, obj): return 'file' def get_size(self, obj): return sys.getsizeof(obj.content) def get_content_type(self, obj): return 'text/markdown' def get_extra(self, obj): return {'version': obj.version} def get_wiki_content(self, obj): return absolute_reverse('wikis:wiki-content', kwargs={ 'wiki_id': obj._id, })
class NodeFilesSerializer(JSONAPISerializer): id = ser.CharField(read_only=True, source='_id') provider = ser.CharField(read_only=True) path = ser.CharField(read_only=True) item_type = ser.CharField(read_only=True) name = ser.CharField(read_only=True) metadata = ser.DictField(read_only=True) class Meta: type_ = 'files' links = LinksField({ 'self': WaterbutlerLink(kwargs={'node_id': '<node_id>'}), 'self_methods': 'valid_self_link_methods', 'related': Link('nodes:node-files', kwargs={'node_id': '<node_id>'}, query_kwargs={'path': '<path>', 'provider': '<provider>'}), }) @staticmethod def valid_self_link_methods(obj): return obj['valid_self_link_methods'] def create(self, validated_data): # TODO pass def update(self, instance, validated_data): # TODO pass
class UserQuickFilesSerializer(QuickFilesSerializer): links = LinksField({ 'info': Link('files:file-detail', kwargs={'file_id': '<_id>'}), 'upload': WaterbutlerLink(), 'delete': WaterbutlerLink(), 'move': WaterbutlerLink(), 'download': WaterbutlerLink(must_be_file=True), })
class UserSerializer(JSONAPISerializer): filterable_fields = frozenset( ['fullname', 'given_name', 'middle_name', 'family_name', 'id']) id = ser.CharField(read_only=True, source='_id') fullname = ser.CharField( help_text='Display name used in the general user interface') given_name = ser.CharField(help_text='For bibliographic citations') middle_name = ser.CharField(source='middle_names', help_text='For bibliographic citations') family_name = ser.CharField(help_text='For bibliographic citations') suffix = ser.CharField(help_text='For bibliographic citations') date_registered = ser.DateTimeField(read_only=True) gravatar_url = ser.CharField( help_text= 'URL for the icon used to identify the user. Relies on http://gravatar.com ' ) employment_institutions = ser.ListField( source='jobs', help_text='An array of dictionaries representing the ' 'places the user has worked') educational_institutions = ser.ListField( source='schools', help_text='An array of dictionaries representing the ' 'places the user has attended school') social_accounts = ser.DictField( source='social', help_text='A dictionary of various social media account ' 'identifiers including an array of user-defined URLs') links = LinksField({ 'html': 'absolute_url', 'nodes': { 'relation': Link('users:user-nodes', kwargs={'user_id': '<pk>'}) } }) class Meta: type_ = 'users' def absolute_url(self, obj): return obj.absolute_url def update(self, instance, validated_data): # TODO pass
class BaseFileSerializer(JSONAPISerializer): filterable_fields = frozenset([ 'id', 'name', 'kind', 'path', 'materialized_path', 'size', 'provider', 'last_touched', 'tags', ]) id = IDField(source='_id', read_only=True) type = TypeField() guid = ser.SerializerMethodField( read_only=True, method_name='get_file_guid', help_text='OSF GUID for this file (if one has been assigned)', ) checkout = CheckoutField() name = ser.CharField( read_only=True, help_text='Display name used in the general user interface') kind = ser.CharField(read_only=True, help_text='Either folder or file') path = ser.CharField( read_only=True, help_text='The unique path used to reference this object') size = ser.SerializerMethodField( read_only=True, help_text='The size of this file at this version') provider = ser.CharField( read_only=True, help_text='The Add-on service this file originates from') materialized_path = ser.CharField( read_only=True, help_text= 'The Unix-style path of this object relative to the provider root', ) last_touched = VersionedDateTimeField( read_only=True, help_text= 'The last time this file had information fetched about it via the OSF') date_modified = ser.SerializerMethodField( read_only=True, help_text='Timestamp when the file was last modified') date_created = ser.SerializerMethodField( read_only=True, help_text='Timestamp when the file was created') extra = ser.SerializerMethodField( read_only=True, help_text='Additional metadata about this file') tags = JSONAPIListField(child=FileTagField(), required=False) current_user_can_comment = ser.SerializerMethodField( help_text='Whether the current user is allowed to post comments') current_version = ser.IntegerField(help_text='Latest file version', read_only=True, source='current_version_number') delete_allowed = ser.BooleanField(read_only=True, required=False) parent_folder = RelationshipField( related_view='files:file-detail', related_view_kwargs={'file_id': '<parent._id>'}, help_text='The folder in which this file exists', ) files = NodeFileHyperLinkField( related_view=lambda node: disambiguate_files_related_view(node), view_lambda_argument='target', related_view_kwargs=lambda filenode: disambiguate_files_related_view_kwargs(filenode), kind='folder', ) versions = NodeFileHyperLinkField( related_view='files:file-versions', related_view_kwargs={'file_id': '<_id>'}, kind='file', ) comments = HideIfPreprint( FileRelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<target._id>'}, related_meta={'unread': 'get_unread_comments_count'}, filter={'target': 'get_file_guid'}, )) metadata_records = FileRelationshipField( related_view='files:metadata-records', related_view_kwargs={'file_id': '<_id>'}, ) links = LinksField({ 'info': Link('files:file-detail', kwargs={'file_id': '<_id>'}), 'move': WaterbutlerLink(), 'upload': WaterbutlerLink(), 'delete': WaterbutlerLink(), 'download': 'get_download_link', 'render': 'get_render_link', 'html': 'absolute_url', 'new_folder': WaterbutlerLink(must_be_folder=True, kind='folder'), }) def absolute_url(self, obj): if obj.is_file: return furl.furl(settings.DOMAIN).set( path=(obj.target._id, 'files', obj.provider, obj.path.lstrip('/')), ).url def get_download_link(self, obj): if obj.is_file: return get_file_download_link( obj, view_only=self.context['request'].query_params.get( 'view_only')) def get_render_link(self, obj): if obj.is_file: mfr_url = get_mfr_url(obj.target, obj.provider) download_url = self.get_download_link(obj) return get_file_render_link(mfr_url, download_url) class Meta: type_ = 'files' def get_size(self, obj): if obj.versions.exists(): self.size = obj.versions.first().size return self.size return None def get_date_modified(self, obj): mod_dt = None if obj.provider == 'osfstorage' and obj.versions.exists(): # Each time an osfstorage file is added or uploaded, a new version object is created with its # date_created equal to the time of the update. The external_modified is the modified date # from the backend the file is stored on. This field refers to the modified date on osfstorage, # so prefer to use the created of the latest version. mod_dt = obj.versions.first().created elif obj.provider != 'osfstorage' and obj.history: mod_dt = obj.history[-1].get('modified', None) if self.context['request'].version >= '2.2' and obj.is_file and mod_dt: return datetime.strftime(mod_dt, '%Y-%m-%dT%H:%M:%S.%fZ') return mod_dt and mod_dt.replace(tzinfo=pytz.utc) def get_date_created(self, obj): creat_dt = None if obj.provider == 'osfstorage' and obj.versions.exists(): creat_dt = obj.versions.last().created elif obj.provider != 'osfstorage' and obj.history: # Non-osfstorage files don't store a created date, so instead get the modified date of the # earliest entry in the file history. creat_dt = obj.history[0].get('modified', None) if self.context[ 'request'].version >= '2.2' and obj.is_file and creat_dt: return datetime.strftime(creat_dt, '%Y-%m-%dT%H:%M:%S.%fZ') return creat_dt and creat_dt.replace(tzinfo=pytz.utc) def get_extra(self, obj): metadata = {} if obj.provider == 'osfstorage' and obj.versions.exists(): metadata = obj.versions.first().metadata elif obj.provider != 'osfstorage' and obj.history: metadata = obj.history[-1].get('extra', {}) extras = {} extras['hashes'] = { # mimic waterbutler response 'md5': metadata.get('md5', None), 'sha256': metadata.get('sha256', None), } if obj.provider == 'osfstorage' and obj.is_file: extras['downloads'] = obj.get_download_count() return extras def get_current_user_can_comment(self, obj): user = self.context['request'].user auth = Auth(user if not user.is_anonymous else None) if isinstance(obj.target, AbstractNode): return obj.target.can_comment(auth) return False def get_unread_comments_count(self, obj): user = self.context['request'].user if user.is_anonymous: return 0 return Comment.find_n_unread(user=user, node=obj.target, page='files', root_id=obj.get_guid()._id) def user_id(self, obj): # NOTE: obj is the user here, the meta field for # Hyperlinks is weird if obj: return obj._id return None def update(self, instance, validated_data): assert isinstance(instance, BaseFileNode), 'Instance must be a BaseFileNode' if instance.provider != 'osfstorage' and 'tags' in validated_data: raise Conflict( 'File service provider {} does not support tags on the OSF.'. format(instance.provider)) auth = get_user_auth(self.context['request']) old_tags = set(instance.tags.values_list('name', flat=True)) if 'tags' in validated_data: current_tags = set(validated_data.pop('tags', [])) else: current_tags = set(old_tags) for new_tag in (current_tags - old_tags): instance.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): instance.remove_tag(deleted_tag, auth=auth) for attr, value in validated_data.items(): if attr == 'checkout': user = self.context['request'].user instance.check_in_or_out(user, value) else: setattr(instance, attr, value) instance.save() return instance def is_valid(self, **kwargs): return super(BaseFileSerializer, self).is_valid(clean_html=False, **kwargs) def get_file_guid(self, obj): if obj: guid = obj.get_guid() if guid: return guid._id return None def get_absolute_url(self, obj): return api_v2_url('files/{}/'.format(obj._id))
class WikiSerializer(JSONAPISerializer): filterable_fields = frozenset(['name', 'date_modified']) id = IDField(source='_id', read_only=True) type = TypeField() name = ser.CharField(source='page_name') kind = ser.SerializerMethodField() size = ser.SerializerMethodField() path = ser.SerializerMethodField() materialized_path = ser.SerializerMethodField(method_name='get_path') date_modified = ser.DateTimeField(source='date') content_type = ser.SerializerMethodField() current_user_can_comment = ser.SerializerMethodField( help_text='Whether the current user is allowed to post comments') extra = ser.SerializerMethodField( help_text='Additional metadata about this wiki') user = RelationshipField(related_view='users:user-detail', related_view_kwargs={'user_id': '<user._id>'}) # LinksField.to_representation adds link to "self" links = LinksField({ 'info': Link('wikis:wiki-detail', kwargs={'wiki_id': '<_id>'}), 'download': 'get_wiki_content' }) class Meta: type_ = 'wikis' def get_absolute_url(self, obj): return obj.get_absolute_url() def get_path(self, obj): return '/{}'.format(obj) def get_kind(self, obj): return 'file' def get_size(self, obj): return sys.getsizeof(obj.content) def get_current_user_can_comment(self, obj): user = self.context['request'].user auth = Auth(user if not user.is_anonymous() else None) return obj.node.can_comment(auth) def get_content_type(self, obj): return 'text/markdown' def get_extra(self, obj): return {'version': obj.version} def get_wiki_content(self, obj): return absolute_reverse( 'wikis:wiki-content', kwargs={ 'wiki_id': obj._id, 'version': self.context['request'].parser_context['kwargs']['version'] })
class UserSerializer(JSONAPISerializer): filterable_fields = frozenset( ['fullname', 'given_name', 'middle_names', 'family_name', 'id']) id = ser.CharField(read_only=True, source='_id') fullname = ser.CharField( required=True, help_text='Display name used in the general user interface') given_name = ser.CharField(required=False, allow_blank=True, help_text='For bibliographic citations') middle_names = ser.CharField(required=False, allow_blank=True, help_text='For bibliographic citations') family_name = ser.CharField(required=False, allow_blank=True, help_text='For bibliographic citations') suffix = ser.CharField(required=False, allow_blank=True, help_text='For bibliographic citations') date_registered = ser.DateTimeField(read_only=True) gravatar_url = ser.URLField( required=False, read_only=True, help_text= 'URL for the icon used to identify the user. Relies on http://gravatar.com ' ) # Social Fields are broken out to get around DRF complex object bug and to make API updating more user friendly. gitHub = ser.CharField(required=False, source='social.github', allow_blank=True, help_text='GitHub Handle') scholar = ser.CharField(required=False, source='social.scholar', allow_blank=True, help_text='Google Scholar Account') personal_website = ser.URLField(required=False, source='social.personal', allow_blank=True, help_text='Personal Website') twitter = ser.CharField(required=False, source='social.twitter', allow_blank=True, help_text='Twitter Handle') linkedIn = ser.CharField(required=False, source='social.linkedIn', allow_blank=True, help_text='LinkedIn Account') impactStory = ser.CharField(required=False, source='social.impactStory', allow_blank=True, help_text='ImpactStory Account') orcid = ser.CharField(required=False, source='social.orcid', allow_blank=True, help_text='ORCID') researcherId = ser.CharField(required=False, source='social.researcherId', allow_blank=True, help_text='ResearcherId Account') links = LinksField({ 'html': 'absolute_url', 'nodes': { 'relation': Link('users:user-nodes', kwargs={'user_id': '<pk>'}) } }) class Meta: type_ = 'users' def absolute_url(self, obj): return obj.absolute_url def update(self, instance, validated_data): assert isinstance(instance, User), 'instance must be a User' for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() return instance
class FileSerializer(JSONAPISerializer): filterable_fields = frozenset([ 'id', 'name', 'node', 'kind', 'path', 'materialized_path', 'size', 'provider', 'last_touched', 'tags', ]) id = IDField(source='_id', read_only=True) type = TypeField() checkout = CheckoutField() name = ser.CharField( read_only=True, help_text='Display name used in the general user interface') kind = ser.CharField(read_only=True, help_text='Either folder or file') path = ser.CharField( read_only=True, help_text='The unique path used to reference this object') size = ser.SerializerMethodField( read_only=True, help_text='The size of this file at this version') provider = ser.CharField( read_only=True, help_text='The Add-on service this file originates from') materialized_path = ser.CharField( read_only=True, help_text= 'The Unix-style path of this object relative to the provider root') last_touched = ser.DateTimeField( read_only=True, help_text= 'The last time this file had information fetched about it via the OSF') date_modified = ser.SerializerMethodField( read_only=True, help_text='Timestamp when the file was last modified') date_created = ser.SerializerMethodField( read_only=True, help_text='Timestamp when the file was created') extra = ser.SerializerMethodField( read_only=True, help_text='Additional metadata about this file') tags = JSONAPIListField(child=FileTagField(), required=False) files = NodeFileHyperLinkField(related_view='nodes:node-files', related_view_kwargs={ 'node_id': '<node_id>', 'path': '<path>', 'provider': '<provider>' }, kind='folder') versions = NodeFileHyperLinkField(related_view='files:file-versions', related_view_kwargs={'file_id': '<_id>'}, kind='file') comments = FileCommentRelationshipField( related_view='nodes:node-comments', related_view_kwargs={'node_id': '<node._id>'}, related_meta={'unread': 'get_unread_comments_count'}, filter={'target': 'get_file_guid'}) links = LinksField({ 'info': Link('files:file-detail', kwargs={'file_id': '<_id>'}), 'move': WaterbutlerLink(), 'upload': WaterbutlerLink(), 'delete': WaterbutlerLink(), 'download': WaterbutlerLink(must_be_file=True), 'new_folder': WaterbutlerLink(must_be_folder=True, kind='folder'), }) class Meta: type_ = 'files' def get_size(self, obj): if obj.versions: return obj.versions[-1].size return None def get_date_modified(self, obj): mod_dt = None if obj.provider == 'osfstorage' and obj.versions: # Each time an osfstorage file is added or uploaded, a new version object is created with its # date_created equal to the time of the update. The date_modified is the modified date # from the backend the file is stored on. This field refers to the modified date on osfstorage, # so prefer to use the date_created of the latest version. mod_dt = obj.versions[-1].date_created elif obj.provider != 'osfstorage' and obj.history: mod_dt = obj.history[-1].get('modified', None) return mod_dt and mod_dt.replace(tzinfo=pytz.utc) def get_date_created(self, obj): creat_dt = None if obj.provider == 'osfstorage' and obj.versions: creat_dt = obj.versions[0].date_created elif obj.provider != 'osfstorage' and obj.history: # Non-osfstorage files don't store a created date, so instead get the modified date of the # earliest entry in the file history. creat_dt = obj.history[0].get('modified', None) return creat_dt and creat_dt.replace(tzinfo=pytz.utc) def get_extra(self, obj): metadata = {} if obj.provider == 'osfstorage' and obj.versions: metadata = obj.versions[-1].metadata elif obj.provider != 'osfstorage' and obj.history: metadata = obj.history[-1].get('extra', {}) extras = {} extras['hashes'] = { # mimic waterbutler response 'md5': metadata.get('md5', None), 'sha256': metadata.get('sha256', None), } return extras def get_unread_comments_count(self, obj): user = self.context['request'].user if user.is_anonymous(): return 0 return Comment.find_n_unread(user=user, node=obj.node, page='files', root_id=obj.get_guid()._id) def user_id(self, obj): # NOTE: obj is the user here, the meta field for # Hyperlinks is weird if obj: return obj._id return None def update(self, instance, validated_data): assert isinstance(instance, FileNode), 'Instance must be a FileNode' if instance.provider != 'osfstorage' and 'tags' in validated_data: raise Conflict( 'File service provider {} does not support tags on the OSF.'. format(instance.provider)) auth = get_user_auth(self.context['request']) old_tags = set([tag._id for tag in instance.tags]) if 'tags' in validated_data: current_tags = set(validated_data.pop('tags', [])) else: current_tags = set(old_tags) for new_tag in (current_tags - old_tags): instance.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): instance.remove_tag(deleted_tag, auth=auth) for attr, value in validated_data.items(): if attr == 'checkout': user = self.context['request'].user instance.check_in_or_out(user, value) else: setattr(instance, attr, value) instance.save() return instance def is_valid(self, **kwargs): return super(FileSerializer, self).is_valid(clean_html=False, **kwargs) def get_file_guid(self, obj): if obj: guid = obj.get_guid() if guid: return guid._id return None def get_absolute_url(self, obj): return api_v2_url('files/{}/'.format(obj._id))
class FileSerializer(JSONAPISerializer): filterable_fields = frozenset([ 'id', 'name', 'node', 'kind', 'path', 'size', 'provider', 'last_touched', ]) id = ser.CharField(read_only=True, source='_id') name = ser.CharField( read_only=True, help_text='Display name used in the general user interface') kind = ser.CharField(read_only=True, help_text='Either folder or file') path = ser.CharField( read_only=True, help_text='The unique path used to reference this object') size = ser.SerializerMethodField( read_only=True, help_text='The size of this file at this version') provider = ser.CharField( read_only=True, help_text='The Add-on service this file originates from') last_touched = ser.DateTimeField( read_only=True, help_text= 'The last time this file had information fetched about it via the OSF') checkout = CheckoutField() files = NodeFileHyperLink(kind='folder', link_type='related', view_name='nodes:node-files', kwargs=('node_id', 'path', 'provider')) versions = NodeFileHyperLink(kind='file', link_type='related', view_name='files:file-versions', kwargs=(('file_id', '_id'), )) links = LinksField({ 'info': Link('files:file-detail', kwargs={'file_id': '<_id>'}), 'move': WaterbutlerLink(), 'upload': WaterbutlerLink(), 'delete': WaterbutlerLink(), 'download': WaterbutlerLink(must_be_file=True), 'new_folder': WaterbutlerLink(must_be_folder=True, kind='folder') }) class Meta: type_ = 'files' def get_size(self, obj): if obj.versions: return obj.versions[-1].size return None def update(self, instance, validated_data): assert isinstance(instance, FileNode), 'Instance must be a FileNode' for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() return instance def is_valid(self, **kwargs): return super(FileSerializer, self).is_valid(clean_html=False, **kwargs)
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 category_choices = Node.CATEGORY_MAP.keys() category_choices_string = ', '.join(["'{}'".format(choice) for choice in category_choices]) filterable_fields = frozenset(['title', 'description', 'public']) id = ser.CharField(read_only=True, source='_id') 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) tags = ser.ListField(child=NodeTagField(), required=False) links = LinksField({ 'html': 'get_absolute_url', 'children': { 'related': Link('nodes:node-children', kwargs={'node_id': '<pk>'}), 'count': 'get_node_count', }, 'contributors': { 'related': Link('nodes:node-contributors', kwargs={'node_id': '<pk>'}), 'count': 'get_contrib_count', }, 'node_links': { 'related': Link('nodes:node-pointers', kwargs={'node_id': '<pk>'}), 'count': 'get_pointers_count', }, 'registrations': { 'related': Link('nodes:node-registrations', kwargs={'node_id': '<pk>'}), 'count': 'get_registration_count', }, 'files': { 'related': Link('nodes:node-files', kwargs={'node_id': '<pk>'}) }, 'parent': { 'self': Link('nodes:node-detail', kwargs={'node_id': '<parent_id>'}) } }) properties = ser.SerializerMethodField(help_text='A dictionary of read-only booleans: registration, collection,' 'and dashboard. Collections are special nodes used by the Project ' 'Organizer to, as you would imagine, organize projects. ' 'A dashboard is a collection node that serves as the root of ' 'Project Organizer collections. Every user will always have ' 'one Dashboard') # TODO: When we have 'admin' permissions, make this writable for admins public = ser.BooleanField(source='is_public', read_only=True, 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', ) # TODO: finish me 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) @staticmethod def get_properties(obj): ret = { 'registration': obj.is_registration, 'collection': obj.is_folder, 'dashboard': obj.is_dashboard, } return ret def create(self, validated_data): node = Node(**validated_data) node.save() return node def update(self, instance, validated_data): """Update instance with the validated data. Requires the request to be in the serializer context. """ assert isinstance(instance, Node), 'instance must be a Node' auth = self.get_user_auth(self.context['request']) for attr, value in validated_data.items(): if attr == 'tags': old_tags = set([tag._id for tag in instance.tags]) if value: current_tags = set(value) else: current_tags = set() for new_tag in (current_tags - old_tags): instance.add_tag(new_tag, auth=auth) for deleted_tag in (old_tags - current_tags): instance.remove_tag(deleted_tag, auth=auth) else: setattr(instance, attr, value) instance.save() return instance
class FileSerializer(JSONAPISerializer): filterable_fields = frozenset([ 'id', 'name', 'node', 'kind', 'path', 'size', 'provider', 'last_touched', ]) id = IDField(source='_id', read_only=True) type = TypeField() checkout = CheckoutField() name = ser.CharField(read_only=True, help_text='Display name used in the general user interface') kind = ser.CharField(read_only=True, help_text='Either folder or file') path = ser.CharField(read_only=True, help_text='The unique path used to reference this object') size = ser.SerializerMethodField(read_only=True, help_text='The size of this file at this version') provider = ser.CharField(read_only=True, help_text='The Add-on service this file originates from') last_touched = ser.DateTimeField(read_only=True, help_text='The last time this file had information fetched about it via the OSF') date_modified = ser.SerializerMethodField(read_only=True, help_text='The size of this file at this version') extra = ser.SerializerMethodField(read_only=True, help_text='Additional metadata about this file') files = NodeFileHyperLinkField( related_view='nodes:node-files', related_view_kwargs={'node_id': '<node_id>', 'path': '<path>', 'provider': '<provider>'}, kind='folder' ) versions = NodeFileHyperLinkField( related_view='files:file-versions', related_view_kwargs={'file_id': '<_id>'}, kind='file' ) links = LinksField({ 'info': Link('files:file-detail', kwargs={'file_id': '<_id>'}), 'move': WaterbutlerLink(), 'upload': WaterbutlerLink(), 'delete': WaterbutlerLink(), 'download': WaterbutlerLink(must_be_file=True), 'new_folder': WaterbutlerLink(must_be_folder=True, kind='folder'), }) class Meta: type_ = 'files' def get_size(self, obj): if obj.versions: return obj.versions[-1].size return None def get_date_modified(self, obj): if obj.versions: if obj.provider == 'osfstorage': # Odd case osfstorage's date created is when the version was create # (when the file was modified) its date modified is referencing whatever backend it is on return obj.versions[-1].date_created return obj.versions[-1].date_modified return None def get_extra(self, obj): extras = {} if obj.versions: metadata = obj.versions[-1].metadata extras['hashes'] = { # mimic waterbutler response 'md5': metadata.get('md5', None), 'sha256': metadata.get('sha256', None), } return extras def user_id(self, obj): # NOTE: obj is the user here, the meta field for # Hyperlinks is weird if obj: return obj._id return None def update(self, instance, validated_data): assert isinstance(instance, FileNode), 'Instance must be a FileNode' for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() return instance def is_valid(self, **kwargs): return super(FileSerializer, self).is_valid(clean_html=False, **kwargs)