diff --git a/submissions/services.py b/submissions/services.py index bd16fb2ae70960507034ca3e46f8b6780a85fe87..4448bea5c2a0d39b01afd1432be65eab797e5898 100644 --- a/submissions/services.py +++ b/submissions/services.py @@ -1,67 +1,122 @@ # Module for making external api calls as needed in the submissions cycle -import feedparser +import feedparser, requests, pprint +from io import BytesIO from .models import * +import re class ArxivCaller(): - def lookup_article(identifier): - # Pre-checks - if same_version_exists(identifier) - return False, "This preprint version has already been submitted to SciPost." + """ Performs an Arxiv article lookup for given identifier """ + + # State of the caller + isvalid = None + errorcode = '' + resubmission = False + arxiv_journal_ref = '' + arxiv_doi = '' + metadata = {} + query_base_url = 'http://export.arxiv.org/api/query?id_list=%s' + identifier_without_vn_nr = '' + identifier_with_vn_nr = '' + version_nr = None + + def __init__(self): + pass + + def is_valid(self): + if self.isvalid is None: + print("Run process() first") + return False + return self.isvalid + + def process(self, identifier): + # ============================= # + # Pre-checks # + # ============================= # + if self.same_version_exists(identifier): + self.errorcode = 'preprint_already_submitted' + self.isvalid = False + return # Split the given identifier in an article identifier and version number - identifier_without_vn_nr = identifier.rpartition('v')[0] - arxiv_vn_nr = int(identifier.rpartition('v')[2]) - - resubmission = False - if previous_submission_undergoing_refereeing(identifier): - errormessage = '<p>There exists a preprint with this arXiv identifier ' - 'but an earlier version number, which is still undergoing ' - 'peer refereeing.</p>' - '<p>A resubmission can only be performed after request ' - 'from the Editor-in-charge. Please wait until the ' - 'closing of the previous refereeing round and ' - 'formulation of the Editorial Recommendation ' - 'before proceeding with a resubmission.</p>' - return False, errormessage - - # Arxiv query - queryurl = ('http://export.arxiv.org/api/query?id_list=%s' - % identifier) - arxiv_response = feedparser.parse(queryurl) + if re.match("^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$", identifier) is None: + self.errorcode = 'bad_identifier' + self.isvalid = False + return + + self.identifier_without_vn_nr = identifier.rpartition('v')[0] + self.identifier_with_vn_nr = identifier + self.version_nr = int(identifier.rpartition('v')[2]) + + previous_submissions = self.different_versions(self.identifier_without_vn_nr) + if previous_submissions: + if previous_submissions[0].status == 'revision_requested': + resubmission = True + else: + self.errorcode = 'previous_submission_undergoing_refereeing' + self.isvalid = False + return + + # ============================= # + # Arxiv query # + # ============================= # + queryurl = (self.query_base_url % identifier) + + try: + req = requests.get(queryurl, timeout=4.0) + except requests.ReadTimeout: + self.errorcode = 'arxiv_timeout' + self.isvalid = False + return + except requests.ConnectionError: + self.errorcode = 'arxiv_timeout' + self.isvalid = False + return + + content = req.content + arxiv_response = feedparser.parse(content) # Check if response has at least one entry - if not 'entries' in arxiv_response - errormessage = 'Bad response from Arxiv.' - return False, errormessage + if req.status_code == 400 or 'entries' not in arxiv_response: + self.errorcode = 'arxiv_bad_request' + self.isvalid = False + return + + # arxiv_response['entries'][0]['title'] == 'Error' # Check if preprint exists - if not preprint_exists(arxiv_response) - errormessage = 'A preprint associated to this identifier does not exist.' - return False, errormessage + if not self.preprint_exists(arxiv_response): + self.errorcode = 'preprint_does_not_exist' + self.isvalid = False + return # Check via journal ref if already published - arxiv_journal_ref = published_journal_ref - if arxiv_journal_ref - errormessage = 'This paper has been published as ' + arxiv_journal_ref + - '. You cannot submit it to SciPost anymore.' - return False, resubmission + self.arxiv_journal_ref = self.published_journal_ref(arxiv_response) + if self.arxiv_journal_ref: + self.errorcode = 'paper_published_journal_ref' + self.isvalid = False + return # Check via DOI if already published - arxiv_doi = published_journal_ref - if arxiv_doi - errormessage = 'This paper has been published under DOI ' + arxiv_doi - + '. You cannot submit it to SciPost anymore.' - return False, errormessage - - return arxiv_response, "" + self.arxiv_doi = self.published_doi(arxiv_response) + if self.arxiv_doi: + self.errorcode = 'paper_published_doi' + self.isvalid = False + return + self.metadata = arxiv_response + self.isvalid = True + return - def same_version_exists(identifier): + def same_version_exists(self, identifier): return Submission.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists() - def previous_submission_undergoing_refereeing(identifier): + def different_versions(self, identifier): + return Submission.objects.filter( + arxiv_identifier_wo_vn_nr=identifier).order_by('-arxiv_vn_nr') + + def check_previous_submissions(self, identifier): previous_submissions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=identifier).order_by('-arxiv_vn_nr') @@ -70,17 +125,17 @@ class ArxivCaller(): else: return False - def preprint_exists(arxiv_response): + def preprint_exists(self, arxiv_response): return 'title' in arxiv_response['entries'][0] - def published_journal_ref(arxiv_response): - if 'arxiv_journal_ref' in arxiv_response['entries'][0] + def published_journal_ref(self, arxiv_response): + if 'arxiv_journal_ref' in arxiv_response['entries'][0]: return arxiv_response['entries'][0]['arxiv_journal_ref'] else: return False - def published_DOI(arxiv_response): - if 'arxiv_doi' in arxiv_response['entries'][0] + def published_doi(self, arxiv_response): + if 'arxiv_doi' in arxiv_response['entries'][0]: return arxiv_response['entries'][0]['arxiv_doi'] else: return False diff --git a/submissions/tests/test_models.py b/submissions/test_models.py similarity index 100% rename from submissions/tests/test_models.py rename to submissions/test_models.py diff --git a/submissions/test_services.py b/submissions/test_services.py new file mode 100644 index 0000000000000000000000000000000000000000..58862466fd4e3fd303b553000199acba282e4c61 --- /dev/null +++ b/submissions/test_services.py @@ -0,0 +1,44 @@ +from django.test import TestCase +from .services import ArxivCaller +import pprint + + +class ArxivCallerTest(TestCase): + + def test_correct_lookup(self): + caller = ArxivCaller() + + caller.process('1611.09574v1') + + self.assertEqual(caller.is_valid(), True) + self.assertIn('entries', caller.metadata) + + def test_errorcode_for_non_existing_paper(self): + caller = ArxivCaller() + + caller.process('2611.09574v1') + self.assertEqual(caller.is_valid(), False) + self.assertEqual(caller.errorcode, 'preprint_does_not_exist') + + def test_errorcode_for_bad_request(self): + caller = ArxivCaller() + + caller.process('161109574v1') + self.assertEqual(caller.is_valid(), False) + self.assertEqual(caller.errorcode, 'arxiv_bad_request') + + def test_errorcode_for_already_published_journal_ref(self): + caller = ArxivCaller() + + caller.process('1412.0006v1') + self.assertEqual(caller.is_valid(), False) + self.assertEqual(caller.errorcode, 'paper_published_journal_ref') + self.assertNotEqual(caller.arxiv_journal_ref, '') + + def test_errorcode_no_version(self): + # Should be already caught in form validation + caller = ArxivCaller() + + caller.process('1412.0006') + self.assertEqual(caller.is_valid(), False) + self.assertEqual(caller.errorcode, 'bad_identifier') diff --git a/submissions/tests/test_views.py b/submissions/test_views.py similarity index 100% rename from submissions/tests/test_views.py rename to submissions/test_views.py diff --git a/submissions/views.py b/submissions/views.py index f7a4ac054a33887fecb3f5c76662125ea437275f..c7b9b2e4d7f7c1861a86b45c76e7b027541bd1c7 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -33,6 +33,7 @@ from scipost.utils import Utils from comments.forms import CommentForm +from .services import ArxivCaller ############### @@ -44,82 +45,32 @@ def prefill_using_identifier(request): if request.method == "POST": identifierform = SubmissionIdentifierForm(request.POST) if identifierform.is_valid(): - # Perform Arxiv query and check if results are OK for submission - metadata, errormessage = lookup_article(identifierform.cleaned_data['identifier']) - - if not metadata: - form = SubmissionForm() - return render(request, 'submissions/submit_manuscript.html', - {'identifierform': identifierform, 'form': form, - 'errormessage': errormessage}) - - is_resubmission = False - resubmessage = '' - previous_submissions = Submission.objects.filter( - arxiv_identifier_wo_vn_nr=identifier_without_vn_nr).order_by('-arxiv_vn_nr') - if previous_submissions.exists(): - # If the Editorial Recommendation hasn't been formulated, ask to wait - if previous_submissions[0].status != 'revision_requested': - errormessage = ('<p>There exists a preprint with this arXiv identifier ' - 'but an earlier version number, which is still undergoing ' - 'peer refereeing.</p>' - '<p>A resubmission can only be performed after request ' - 'from the Editor-in-charge. Please wait until the ' - 'closing of the previous refereeing round and ' - 'formulation of the Editorial Recommendation ' - 'before proceeding with a resubmission.</p>') - return render(request, 'scipost/error.html', - {'errormessage': mark_safe(errormessage)}) - is_resubmission = True - resubmessage = ('There already exists a preprint with this arXiv identifier ' - 'but a different version number. \nYour Submission will be ' - 'handled as a resubmission.') - try: - queryurl = ('http://export.arxiv.org/api/query?id_list=%s' - % identifierform.cleaned_data['identifier']) - arxivquery = feedparser.parse(queryurl) - # Flag error if preprint doesn't exist - try: - test = arxivquery['entries'][0]['title'] - except KeyError: - errormessage = 'A preprint associated to this identifier does not exist.' - except: - pass - - # If paper has been published, should comment on published version - try: - arxiv_journal_ref = arxivquery['entries'][0]['arxiv_journal_ref'] - errormessage = ('This paper has been published as ' + arxiv_journal_ref + - '. You cannot submit it to SciPost anymore.') - except: - pass - try: - arxiv_doi = arxivquery['entries'][0]['arxiv_doi'] - errormessage = ('This paper has been published under DOI ' + arxiv_DOI - + '. You cannot submit it to SciPost anymore.') - except: - pass - if errormessage != '': - form = SubmissionForm() - context = {'identifierform': identifierform, 'form': form, - 'errormessage': errormessage} - return render(request, 'submissions/submit_manuscript.html', context) - - metadata = arxivquery - title = arxivquery['entries'][0]['title'] - authorlist = arxivquery['entries'][0]['authors'][0]['name'] - for author in arxivquery['entries'][0]['authors'][1:]: + # Use the ArxivCaller class to make the API calls + caller = ArxivCaller() + caller.process(identifierform.cleaned_data['identifier']) + + if caller.is_valid(): + # Arxiv response is valid and can be shown + + metadata = caller.metadata + is_resubmission = caller.resubmission + title = metadata['entries'][0]['title'] + authorlist = metadata['entries'][0]['authors'][0]['name'] + for author in metadata['entries'][0]['authors'][1:]: authorlist += ', ' + author['name'] - arxiv_link = arxivquery['entries'][0]['id'] - abstract = arxivquery['entries'][0]['summary'] - initialdata={'is_resubmission': is_resubmission, - 'metadata': metadata, - 'title': title, 'author_list': authorlist, - 'arxiv_identifier_w_vn_nr': identifierform.cleaned_data['identifier'], - 'arxiv_identifier_wo_vn_nr': identifier_without_vn_nr, - 'arxiv_vn_nr': arxiv_vn_nr, - 'arxiv_link': arxiv_link, 'abstract': abstract} + arxiv_link = metadata['entries'][0]['id'] + abstract = metadata['entries'][0]['summary'] + initialdata = {'is_resubmission': is_resubmission, + 'metadata': metadata, + 'title': title, 'author_list': authorlist, + 'arxiv_identifier_w_vn_nr': caller.identifier_with_vn_nr, + 'arxiv_identifier_wo_vn_nr': caller.identifier_without_vn_nr, + 'arxiv_vn_nr': caller.version_nr, + 'arxiv_link': arxiv_link, 'abstract': abstract} if is_resubmission: + resubmessage = ('There already exists a preprint with this arXiv identifier ' + 'but a different version number. \nYour Submission will be ' + 'handled as a resubmission.') initialdata['submitted_to_journal'] = previous_submissions[0].submitted_to_journal initialdata['submission_type'] = previous_submissions[0].submission_type initialdata['discipline'] = previous_submissions[0].discipline @@ -128,17 +79,46 @@ def prefill_using_identifier(request): initialdata['secondary_areas'] = previous_submissions[0].secondary_areas initialdata['referees_suggested'] = previous_submissions[0].referees_suggested initialdata['referees_flagged'] = previous_submissions[0].referees_flagged + else: + resubmessage = '' + form = SubmissionForm(initial=initialdata) context = {'identifierform': identifierform, 'form': form, 'resubmessage': resubmessage} return render(request, 'submissions/submit_manuscript.html', context) - except: - print("Unexpected error in prefill_using_identifier:", sys.exc_info()[0]) - context = {'identifierform': identifierform, - 'form': SubmissionForm(), - 'errormessage': errormessage,} - return render(request, 'submissions/submit_manuscript.html', context) + + else: + # Arxiv response is not valid + errormessages = { + 'preprint_does_not_exist': + 'A preprint associated to this identifier does not exist.', + 'paper_published_journal_ref': + ('This paper has been published as ' + caller.arxiv_journal_ref + + '. You cannot submit it to SciPost anymore.'), + 'paper_published_doi': + ('This paper has been published under DOI ' + caller.arxiv_doi + + '. You cannot submit it to SciPost anymore.'), + 'arxiv_timeout': 'Arxiv did not respond in time. Please try again later', + 'arxiv_bad_request': + ('There was an error with requesting identifier ' + + caller.identifier_with_vn_nr + + ' from Arxiv. Please check the identifier and try again.'), + 'previous_submission_undergoing_refereeing': + ('There exists a preprint with this arXiv identifier ' + 'but an earlier version number, which is still undergoing ' + 'peer refereeing.' + 'A resubmission can only be performed after request ' + 'from the Editor-in-charge. Please wait until the ' + 'closing of the previous refereeing round and ' + 'formulation of the Editorial Recommendation ' + 'before proceeding with a resubmission.') + } + + identifierform.add_error(None, errormessages[caller.errorcode]) + form = SubmissionForm() + return render(request, 'submissions/submit_manuscript.html', + {'identifierform': identifierform, 'form': form}) else: form = SubmissionForm() return render(request, 'submissions/submit_manuscript.html',