PageRenderTime 64ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/website/addons/mendeley/model.py

https://gitlab.com/doublebits/osf.io
Python | 397 lines | 381 code | 14 blank | 2 comment | 1 complexity | 8d48f9c1f8cef8d1034515f2c6e3130b MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. import time
  3. import mendeley
  4. from mendeley.exception import MendeleyApiException
  5. from modularodm import fields
  6. from website.addons.base import AddonOAuthNodeSettingsBase
  7. from website.addons.base import AddonOAuthUserSettingsBase
  8. from website.addons.citations.utils import serialize_folder
  9. from website.addons.mendeley import serializer
  10. from website.addons.mendeley import settings
  11. from website.addons.mendeley.api import APISession
  12. from website.oauth.models import ExternalProvider
  13. from website.util import web_url_for
  14. from framework.exceptions import HTTPError
  15. class Mendeley(ExternalProvider):
  16. name = 'Mendeley'
  17. short_name = 'mendeley'
  18. client_id = settings.MENDELEY_CLIENT_ID
  19. client_secret = settings.MENDELEY_CLIENT_SECRET
  20. auth_url_base = 'https://api.mendeley.com/oauth/authorize'
  21. callback_url = 'https://api.mendeley.com/oauth/token'
  22. default_scopes = ['all']
  23. _client = None
  24. def handle_callback(self, response):
  25. client = self._get_client(response)
  26. # make a second request for the Mendeley user's ID and name
  27. profile = client.profiles.me
  28. return {
  29. 'provider_id': profile.id,
  30. 'display_name': profile.display_name,
  31. 'profile_url': profile.link,
  32. }
  33. def _get_client(self, credentials):
  34. if not self._client:
  35. partial = mendeley.Mendeley(
  36. client_id=self.client_id,
  37. client_secret=self.client_secret,
  38. redirect_uri=web_url_for('oauth_callback',
  39. service_name='mendeley',
  40. _absolute=True),
  41. )
  42. self._client = APISession(partial, credentials)
  43. return self._client
  44. def _get_folders(self):
  45. """Get a list of a user's folders"""
  46. client = self.client
  47. return client.folders.list().items
  48. @property
  49. def client(self):
  50. """An API session with Mendeley"""
  51. if not self._client:
  52. self._client = self._get_client({
  53. 'access_token': self.account.oauth_key,
  54. 'refresh_token': self.account.refresh_token,
  55. 'expires_at': time.mktime(self.account.expires_at.timetuple()),
  56. 'token_type': 'bearer',
  57. })
  58. #Check if Mendeley can be accessed
  59. try:
  60. self._client.folders.list()
  61. except MendeleyApiException as error:
  62. self._client = None
  63. if error.status == 403:
  64. raise HTTPError(403)
  65. else:
  66. raise HTTPError(error.status)
  67. return self._client
  68. def citation_lists(self, extract_folder):
  69. """List of CitationList objects, derived from Mendeley folders"""
  70. folders = self._get_folders()
  71. # TODO: Verify OAuth access to each folder
  72. all_documents = serialize_folder(
  73. 'All Documents',
  74. id='ROOT',
  75. parent_id='__'
  76. )
  77. serialized_folders = [
  78. extract_folder(each)
  79. for each in folders
  80. ]
  81. return [all_documents] + serialized_folders
  82. def get_list(self, list_id='ROOT'):
  83. """Get a single CitationList
  84. :param str list_id: ID for a Mendeley folder. Optional.
  85. :return CitationList: CitationList for the folder, or for all documents
  86. """
  87. if list_id == 'ROOT':
  88. folder = None
  89. else:
  90. folder = self.client.folders.get(list_id)
  91. if folder:
  92. return self._citations_for_mendeley_folder(folder)
  93. return self._citations_for_mendeley_user()
  94. def _folder_metadata(self, folder_id):
  95. folder = self.client.folders.get(folder_id)
  96. return folder
  97. def _citations_for_mendeley_folder(self, folder):
  98. document_ids = [
  99. document.id
  100. for document in folder.documents.iter(page_size=500)
  101. ]
  102. citations = {
  103. citation['id']: citation
  104. for citation in self._citations_for_mendeley_user()
  105. }
  106. return map(lambda id: citations[id], document_ids)
  107. def _citations_for_mendeley_user(self):
  108. documents = self.client.documents.iter(page_size=500)
  109. return [
  110. self._citation_for_mendeley_document(document)
  111. for document in documents
  112. ]
  113. def _citation_for_mendeley_document(self, document):
  114. """Mendeley document to ``website.citations.models.Citation``
  115. :param BaseDocument document:
  116. An instance of ``mendeley.models.base_document.BaseDocument``
  117. :return Citation:
  118. """
  119. csl = {
  120. 'id': document.json.get('id')
  121. }
  122. CSL_TYPE_MAP = {
  123. 'book_section': 'chapter',
  124. 'case': 'legal_case',
  125. 'computer_program': 'article',
  126. 'conference_proceedings': 'paper-conference',
  127. 'encyclopedia_article': 'entry-encyclopedia',
  128. 'film': 'motion_picture',
  129. 'generic': 'article',
  130. 'hearing': 'speech',
  131. 'journal': 'article-journal',
  132. 'magazine_article': 'article-magazine',
  133. 'newspaper_article': 'article-newspaper',
  134. 'statute': 'legislation',
  135. 'television_broadcast': 'broadcast',
  136. 'web_page': 'webpage',
  137. 'working_paper': 'report'
  138. }
  139. csl_type = document.json.get('type')
  140. if csl_type in CSL_TYPE_MAP:
  141. csl['type'] = CSL_TYPE_MAP[csl_type]
  142. else:
  143. csl['type'] = 'article'
  144. if document.json.get('abstract'):
  145. csl['abstract'] = document.json.get('abstract')
  146. if document.json.get('accessed'):
  147. csl['accessed'] = document.json.get('accessed')
  148. if document.json.get('authors'):
  149. csl['author'] = [
  150. {
  151. 'given': person.get('first_name'),
  152. 'family': person.get('last_name'),
  153. } for person in document.json.get('authors')
  154. ]
  155. if document.json.get('chapter'):
  156. csl['chapter-number'] = document.json.get('chapter')
  157. if document.json.get('city') and document.json.get('country'):
  158. csl['publisher-place'] = document.json.get('city') + ", " + document.json.get('country')
  159. elif document.json.get('city'):
  160. csl['publisher-place'] = document.json.get('city')
  161. elif document.json.get('country'):
  162. csl['publisher-place'] = document.json.get('country')
  163. if document.json.get('edition'):
  164. csl['edition'] = document.json.get('edition')
  165. if document.json.get('editors'):
  166. csl['editor'] = [
  167. {
  168. 'given': person.get('first_name'),
  169. 'family': person.get('last_name'),
  170. } for person in document.json.get('editors')
  171. ]
  172. if document.json.get('genre'):
  173. csl['genre'] = document.json.get('genre')
  174. # gather identifiers
  175. idents = document.json.get('identifiers')
  176. if idents is not None:
  177. if idents.get('doi'):
  178. csl['DOI'] = idents.get('doi')
  179. if idents.get('isbn'):
  180. csl['ISBN'] = idents.get('isbn')
  181. if idents.get('issn'):
  182. csl['ISSN'] = idents.get('issn')
  183. if idents.get('pmid'):
  184. csl['PMID'] = idents.get('pmid')
  185. if document.json.get('issue'):
  186. csl['issue'] = document.json.get('issue')
  187. if document.json.get('language'):
  188. csl['language'] = document.json.get('language')
  189. if document.json.get('medium'):
  190. csl['medium'] = document.json.get('medium')
  191. if document.json.get('pages'):
  192. csl['page'] = document.json.get('pages')
  193. if document.json.get('publisher'):
  194. csl['publisher'] = document.json.get('publisher')
  195. if csl_type == 'thesis':
  196. csl['publisher'] = document.json.get('institution')
  197. if document.json.get('revision'):
  198. csl['number'] = document.json.get('revision')
  199. if document.json.get('series'):
  200. csl['collection-title'] = document.json.get('series')
  201. if document.json.get('series_editor'):
  202. csl['collection-editor'] = document.json.get('series_editor')
  203. if document.json.get('short_title'):
  204. csl['shortTitle'] = document.json.get('short_title')
  205. if document.json.get('source'):
  206. csl['container-title'] = document.json.get('source')
  207. if document.json.get('title'):
  208. csl['title'] = document.json.get('title')
  209. if document.json.get('volume'):
  210. csl['volume'] = document.json.get('volume')
  211. urls = document.json.get('websites', [])
  212. if urls:
  213. csl['URL'] = urls[0]
  214. if document.json.get('year'):
  215. csl['issued'] = {'date-parts': [[document.json.get('year')]]}
  216. return csl
  217. class MendeleyUserSettings(AddonOAuthUserSettingsBase):
  218. oauth_provider = Mendeley
  219. serializer = serializer.MendeleySerializer
  220. class MendeleyNodeSettings(AddonOAuthNodeSettingsBase):
  221. oauth_provider = Mendeley
  222. serializer = serializer.MendeleySerializer
  223. mendeley_list_id = fields.StringField()
  224. _api = None
  225. @property
  226. def api(self):
  227. """authenticated ExternalProvider instance"""
  228. if self._api is None:
  229. self._api = Mendeley()
  230. self._api.account = self.external_account
  231. return self._api
  232. @property
  233. def complete(self):
  234. return bool(self.has_auth and self.user_settings.verify_oauth_access(
  235. node=self.owner,
  236. external_account=self.external_account,
  237. metadata={'folder': self.mendeley_list_id},
  238. ))
  239. @property
  240. def selected_folder_name(self):
  241. if self.mendeley_list_id is None:
  242. return ''
  243. elif self.mendeley_list_id == 'ROOT':
  244. return 'All Documents'
  245. else:
  246. folder = self.api._folder_metadata(self.mendeley_list_id)
  247. return folder.name
  248. @property
  249. def root_folder(self):
  250. root = serialize_folder(
  251. 'All Documents',
  252. id='ROOT',
  253. parent_id='__'
  254. )
  255. root['kind'] = 'folder'
  256. return root
  257. @property
  258. def provider_name(self):
  259. return 'mendeley'
  260. @property
  261. def folder_id(self):
  262. return self.mendeley_list_id
  263. @property
  264. def folder_name(self):
  265. return self.selected_folder_name
  266. @property
  267. def folder_path(self):
  268. return self.selected_folder_name
  269. def clear_auth(self):
  270. self.mendeley_list_id = None
  271. return super(MendeleyNodeSettings, self).clear_auth()
  272. def deauthorize(self, auth=None, add_log=True):
  273. """Remove user authorization from this node and log the event."""
  274. if add_log:
  275. self.owner.add_log(
  276. 'mendeley_node_deauthorized',
  277. params={
  278. 'project': self.owner.parent_id,
  279. 'node': self.owner._id,
  280. },
  281. auth=auth,
  282. )
  283. self.clear_auth()
  284. self.save()
  285. def set_auth(self, *args, **kwargs):
  286. self.mendeley_list_id = None
  287. return super(MendeleyNodeSettings, self).set_auth(*args, **kwargs)
  288. def set_target_folder(self, mendeley_list_id, mendeley_list_name, auth):
  289. """Configure this addon to point to a Mendeley folder
  290. :param str mendeley_list_id:
  291. :param ExternalAccount external_account:
  292. :param User user:
  293. """
  294. # Tell the user's addon settings that this node is connecting
  295. self.user_settings.grant_oauth_access(
  296. node=self.owner,
  297. external_account=self.external_account,
  298. metadata={'folder': mendeley_list_id}
  299. )
  300. self.user_settings.save()
  301. # update this instance
  302. self.mendeley_list_id = mendeley_list_id
  303. self.save()
  304. self.owner.add_log(
  305. 'mendeley_folder_selected',
  306. params={
  307. 'project': self.owner.parent_id,
  308. 'node': self.owner._id,
  309. 'folder_id': mendeley_list_id,
  310. 'folder_name': mendeley_list_name,
  311. },
  312. auth=auth,
  313. )