diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 0ca208465dd5f4706ee6a3421d8bb91d4eee9e0c..62c8ff2aa6d133346ce7476f8fcb4e83617fbba7 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -313,3 +313,42 @@ AUTH_PASSWORD_VALIDATORS = [ ] CSRF_FAILURE_VIEW = 'scipost.views.csrf_failure' + +# Logging +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '[%(asctime)s] %(levelname)s | %(message)s' + }, + }, + 'handlers': { + 'scipost_file_arxiv': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': '/path/to/logs/arxiv.log', + 'formatter': 'verbose', + }, + 'scipost_file_doi': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': '/path/to/logs/doi.log', + 'formatter': 'verbose', + }, + }, + 'loggers': { + 'scipost.services.arxiv': { + 'handlers': ['scipost_file_arxiv'], + 'level': 'INFO', + 'propagate': True, + 'formatter': 'simple', + }, + 'scipost.services.doi': { + 'handlers': ['scipost_file_doi'], + 'level': 'INFO', + 'propagate': True, + 'formatter': 'simple', + }, + }, +} diff --git a/SciPost_v1/settings/local_JSC.py b/SciPost_v1/settings/local_JSC.py index f4dcbe1891cd9a9b8ba42739749f2528e168a2f0..843fea24255bb1b2521b3144f4d0f173eb79d460 100644 --- a/SciPost_v1/settings/local_JSC.py +++ b/SciPost_v1/settings/local_JSC.py @@ -11,3 +11,7 @@ WEBPACK_LOADER['DEFAULT']['BUNDLE_DIR_NAME'] =\ MAILCHIMP_API_USER = get_secret("MAILCHIMP_API_USER") MAILCHIMP_API_KEY = get_secret("MAILCHIMP_API_KEY") + +# Logging +LOGGING['handlers']['scipost_file_arxiv']['filename'] = '/Users/jscaux/Sites/SciPost.org/scipost_v1/local_files/logs/arxiv.log' +LOGGING['handlers']['scipost_file_doi']['filename'] = '/Users/jscaux/Sites/SciPost.org/scipost_v1/local_files/logs/doi.log' diff --git a/SciPost_v1/settings/local_jorran.py b/SciPost_v1/settings/local_jorran.py index cd2ed99b46436673da26e9b69da2ec69da258b0d..b458216b2d898d444ce1e586d892d86c76c97410 100644 --- a/SciPost_v1/settings/local_jorran.py +++ b/SciPost_v1/settings/local_jorran.py @@ -26,3 +26,7 @@ DATABASES['default']['PORT'] = '5433' # iThenticate ITHENTICATE_USERNAME = get_secret('ITHENTICATE_USERNAME') ITHENTICATE_PASSWORD = get_secret('ITHENTICATE_PASSWORD') + +# Logging +LOGGING['handlers']['scipost_file_arxiv']['filename'] = '/Users/jorranwit/Develop/SciPost/SciPost_v1/logs/arxiv.log' +LOGGING['handlers']['scipost_file_doi']['filename'] = '/Users/jorranwit/Develop/SciPost/SciPost_v1/logs/doi.log' diff --git a/SciPost_v1/settings/production.py b/SciPost_v1/settings/production.py index ffba29aff8e844946a5124210c21372395a849c5..4d6bb7a6c1f88119eebe06c08b530fb341c53450 100644 --- a/SciPost_v1/settings/production.py +++ b/SciPost_v1/settings/production.py @@ -44,3 +44,7 @@ MAILCHIMP_API_KEY = get_secret("MAILCHIMP_API_KEY") # iThenticate ITHENTICATE_USERNAME = get_secret('ITHENTICATE_USERNAME') ITHENTICATE_PASSWORD = get_secret('ITHENTICATE_PASSWORD') + +# Logging +LOGGING['handlers']['scipost_file_arxiv']['filename'] = '/home/scipost/webapps/scipost/logs/arxiv.log' +LOGGING['handlers']['scipost_file_doi']['filename'] = '/home/scipost/webapps/scipost/logs/doi.log' diff --git a/journals/migrations/0003_auto_20180117_2323.py b/journals/migrations/0003_auto_20180117_2323.py new file mode 100644 index 0000000000000000000000000000000000000000..5020b0dab1564a5cd8d66e0949cf841480621828 --- /dev/null +++ b/journals/migrations/0003_auto_20180117_2323.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-01-17 22:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0002_auto_20171229_1435'), + ] + + operations = [ + migrations.AlterField( + model_name='deposit', + name='doi_batch_id', + field=models.CharField(max_length=40), + ), + migrations.AlterField( + model_name='deposit', + name='metadata_xml', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='deposit', + name='response_text', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='deposit', + name='timestamp', + field=models.CharField(max_length=40), + ), + ] diff --git a/journals/models.py b/journals/models.py index 9ffed4c252f4da7890dd28034a3bfd1e81831c33..ad42c6973258bbf8f70bf7905ac98985593925a2 100644 --- a/journals/models.py +++ b/journals/models.py @@ -226,20 +226,22 @@ class Deposit(models.Model): All deposit history is thus contained here. """ publication = models.ForeignKey(Publication, on_delete=models.CASCADE) - timestamp = models.CharField(max_length=40, default='') - doi_batch_id = models.CharField(max_length=40, default='') - metadata_xml = models.TextField(blank=True, null=True) + timestamp = models.CharField(max_length=40) + doi_batch_id = models.CharField(max_length=40) + metadata_xml = models.TextField(blank=True) metadata_xml_file = models.FileField(blank=True, null=True, max_length=512) deposition_date = models.DateTimeField(blank=True, null=True) - response_text = models.TextField(blank=True, null=True) + response_text = models.TextField(blank=True) deposit_successful = models.NullBooleanField(default=None) class Meta: ordering = ['-timestamp'] def __str__(self): - return (self.deposition_date.strftime('%Y-%m-%D') + - ' for ' + self.publication.doi_label) + _str = '' + if self.deposition_date: + _str += '%s for ' % self.deposition_date.strftime('%Y-%m-%D') + return _str + self.publication.doi_label class DOAJDeposit(models.Model): diff --git a/journals/views.py b/journals/views.py index ae5562dfd3e6e727cb706c789c15da175f215b32..81ebaebce9d3cb16d9cca586d195158d00c8aafc 100644 --- a/journals/views.py +++ b/journals/views.py @@ -3,13 +3,13 @@ import json import os import random import requests +import shutil import string import xml.etree.ElementTree as ET from django.contrib.auth.decorators import login_required from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse -from django.core.files.base import ContentFile from django.conf import settings from django.contrib import messages from django.http import Http404 @@ -719,6 +719,11 @@ def metadata_xml_deposit(request, doi_label, option='test'): Makes use of the python requests module. """ publication = get_object_or_404(Publication, doi_label=doi_label) + + if publication.metadata_xml is None: + errormessage = 'This publication has no metadata. Produce it first before saving it.' + return render(request, 'scipost/error.html', context={'errormessage': errormessage}) + timestamp = (publication.metadata_xml.partition( '<timestamp>'))[2].partition('</timestamp>')[0] doi_batch_id = (publication.metadata_xml.partition( @@ -726,46 +731,58 @@ def metadata_xml_deposit(request, doi_label, option='test'): path = (settings.MEDIA_ROOT + publication.in_issue.path + '/' + publication.get_paper_nr() + '/' + publication.doi_label.replace('.', '_') + '_Crossref_' + timestamp + '.xml') + if os.path.isfile(path): errormessage = 'The metadata file for this metadata timestamp already exists' return render(request, 'scipost/error.html', context={'errormessage': errormessage}) + if option == 'deposit' and not settings.DEBUG: # CAUTION: Real deposit only on production (non-debug-mode) url = 'http://doi.crossref.org/servlet/deposit' else: url = 'http://test.crossref.org/servlet/deposit' - if publication.metadata_xml is None: - errormessage = 'This publication has no metadata. Produce it first before saving it.' - return render(request, 'scipost/error.html', context={'errormessage': errormessage}) # First perform the actual deposit to Crossref params = { 'operation': 'doMDUpload', 'login_id': settings.CROSSREF_LOGIN_ID, 'login_passwd': settings.CROSSREF_LOGIN_PASSWORD, } - files = {'fname': ('metadata.xml', publication.metadata_xml, 'multipart/form-data')} + files = { + 'fname': ('metadata.xml', publication.metadata_xml.encode('utf-8'), 'multipart/form-data') + } r = requests.post(url, params=params, files=files) response_headers = r.headers response_text = r.text # Then create the associated Deposit object (saving the metadata to a file) if option == 'deposit': - content = ContentFile(publication.metadata_xml) deposit = Deposit(publication=publication, timestamp=timestamp, doi_batch_id=doi_batch_id, metadata_xml=publication.metadata_xml, deposition_date=timezone.now()) - deposit.metadata_xml_file.save(path, content) deposit.response_text = r.text + + # Save the filename with timestamp + path_with_timestamp = '{issue}/{paper}/{doi}_Crossref_{timestamp}.xml'.format( + issue=publication.in_issue.path, + paper=publication.get_paper_nr(), + doi=publication.doi_label.replace('.', '_'), + timestamp=timestamp) + f = open(settings.MEDIA_ROOT + path_with_timestamp, 'w') + f.write(publication.metadata_xml) + f.close() + + # Copy file + path_without_timestamp = '{issue}/{paper}/{doi}_Crossref.xml'.format( + issue=publication.in_issue.path, + paper=publication.get_paper_nr(), + doi=publication.doi_label.replace('.', '_')) + shutil.copyfile(settings.MEDIA_ROOT + path_with_timestamp, + settings.MEDIA_ROOT + path_without_timestamp) + + deposit.metadata_xml_file = path_with_timestamp deposit.save() publication.latest_crossref_deposit = timezone.now() publication.save() - # Save a copy to the filename without timestamp - path1 = (settings.MEDIA_ROOT + publication.in_issue.path + '/' - + publication.get_paper_nr() + '/' + publication.doi_label.replace('.', '_') - + '_Crossref.xml') - f = open(path1, 'w') - f.write(publication.metadata_xml) - f.close() context = { 'option': option, @@ -807,6 +824,7 @@ def metadata_DOAJ_deposit(request, doi_label): Makes use of the python requests module. """ publication = get_object_or_404(Publication, doi_label=doi_label) + if not publication.metadata_DOAJ: messages.warning(request, '<h3>%s</h3>Failed: please first produce ' 'DOAJ metadata before depositing.' % publication.doi_label) @@ -820,8 +838,8 @@ def metadata_DOAJ_deposit(request, doi_label): if os.path.isfile(path): errormessage = 'The metadata file for this metadata timestamp already exists' return render(request, 'scipost/error.html', context={'errormessage': errormessage}) - url = 'https://doaj.org/api/v1/articles' + url = 'https://doaj.org/api/v1/articles' params = { 'api_key': settings.DOAJ_API_KEY, } @@ -833,21 +851,32 @@ def metadata_DOAJ_deposit(request, doi_label): publication.doi_label, r.text)) # Then create the associated Deposit object (saving the metadata to a file) - content = ContentFile(json.dumps(publication.metadata_DOAJ)) deposit = DOAJDeposit(publication=publication, timestamp=timestamp, metadata_DOAJ=publication.metadata_DOAJ, deposition_date=timezone.now()) - deposit.metadata_DOAJ_file.save(path, content) deposit.response_text = r.text - deposit.save() - # Save a copy to the filename without timestamp - path1 = (settings.MEDIA_ROOT + publication.in_issue.path + '/' - + publication.get_paper_nr() + '/' + publication.doi_label.replace('.', '_') - + '_DOAJ.json') - f = open(path1, 'w') + # Save a copy to the filename with and without timestamp + path_with_timestamp = '{issue}/{paper}/{doi}_DOAJ_{timestamp}.json'.format( + issue=publication.in_issue.path, + paper=publication.get_paper_nr(), + doi=publication.doi_label.replace('.', '_'), + timestamp=timestamp) + f = open(settings.MEDIA_ROOT + path_with_timestamp, 'w') f.write(json.dumps(publication.metadata_DOAJ)) f.close() + # Copy file + path_without_timestamp = '{issue}/{paper}/{doi}_DOAJ.json'.format( + issue=publication.in_issue.path, + paper=publication.get_paper_nr(), + doi=publication.doi_label.replace('.', '_')) + shutil.copyfile(settings.MEDIA_ROOT + path_with_timestamp, + settings.MEDIA_ROOT + path_without_timestamp) + + # Save the database entry + deposit.metadata_DOAJ_file = path_with_timestamp + deposit.save() + messages.success(request, '<h3>%s</h3>Successfull deposit of metadata DOAJ.' % publication.doi_label) return redirect(reverse('journals:manage_metadata', @@ -863,7 +892,7 @@ def mark_doaj_deposit_success(request, deposit_id, success): deposit.deposit_successful = False deposit.save() return redirect(reverse('journals:manage_metadata', - kwargs={'doi_label': deposit.publication.doi_label})) + kwargs={'doi_label': deposit.publication.doi_label})) @permission_required('scipost.can_publish_accepted_submission', return_403=True) diff --git a/scipost/services.py b/scipost/services.py index 3df2ce70fb903edf7ea290f81830ce69094d566c..1e7a9c06881035b2a03440c817a499693649ef95 100644 --- a/scipost/services.py +++ b/scipost/services.py @@ -3,11 +3,17 @@ import feedparser import requests import datetime import dateutil.parser +import logging + +arxiv_logger = logging.getLogger('scipost.services.arxiv') +doi_logger = logging.getLogger('scipost.services.doi') class DOICaller: def __init__(self, doi_string): self.doi_string = doi_string + doi_logger.info('New DOI call for %s' % doi_string) + self._call_crosslink() if self.is_valid: self._format_data() @@ -15,12 +21,24 @@ class DOICaller: def _call_crosslink(self): url = 'http://api.crossref.org/works/%s' % self.doi_string request = requests.get(url) + + doi_logger.info('GET [{doi}] [request] | {url}'.format( + doi=self.doi_string, + url=url, + )) + if request.ok: self.is_valid = True self._crossref_data = request.json()['message'] else: self.is_valid = False + doi_logger.info('GET [{doi}] [response {valid}] | {response}'.format( + doi=self.doi_string, + valid='VALID' if self.is_valid else 'INVALID', + response=request.text, + )) + def _format_data(self): data = self._crossref_data title = data['title'][0] @@ -41,6 +59,11 @@ class DOICaller: 'pub_date': pub_date, } + doi_logger.info('GET [{doi}] [formatted data] | {data}'.format( + doi=self.doi_string, + data=self.data, + )) + def _get_pages(self, data): # For Physical Review pages = data.get('article-number', '') @@ -68,6 +91,7 @@ class ArxivCaller: def __init__(self, identifier): self.identifier = identifier + arxiv_logger.info('New ArXiv call for identifier %s' % identifier) self._call_arxiv() if self.is_valid: self._format_data() @@ -76,14 +100,25 @@ class ArxivCaller: url = self.query_base_url % self.identifier request = requests.get(url) response_content = feedparser.parse(request.content) - arxiv_data = response_content['entries'][0] - if self._search_result_present(arxiv_data): + arxiv_logger.info('GET [{arxiv}] [request] | {url}'.format( + arxiv=self.identifier, + url=url, + )) + + if self._search_result_present(response_content): + arxiv_data = response_content['entries'][0] self.is_valid = True self._arxiv_data = arxiv_data self.metadata = response_content else: self.is_valid = False + arxiv_logger.info('GET [{arxiv}] [response {valid}] | {response}'.format( + arxiv=self.identifier, + valid='VALID' if self.is_valid else 'INVALID', + response=response_content, + )) + def _format_data(self): data = self._arxiv_data title = data['title'] @@ -102,6 +137,12 @@ class ArxivCaller: 'abstract': abstract, # Duplicate for Commentary/Submission cross-compatibility 'pub_date': pub_date, } + arxiv_logger.info('GET [{arxiv}] [formatted data] | {data}'.format( + arxiv=self.identifier, + data=self.data, + )) def _search_result_present(self, data): - return 'title' in data + if len(data.get('entries', [])) > 0: + return 'title' in data['entries'][0] + return False diff --git a/scipost/static/scipost/assets/css/_navbar.scss b/scipost/static/scipost/assets/css/_navbar.scss index c74f30a0f50d61d9ff505251fbcd1dfa3548353d..8b32a2115a3afdcae2c6d4f521dbdf295a5c026e 100644 --- a/scipost/static/scipost/assets/css/_navbar.scss +++ b/scipost/static/scipost/assets/css/_navbar.scss @@ -15,10 +15,10 @@ .navbar-nav { flex-direction: row; - overflow: scroll; - -ms-overflow-style: none; + overflow: auto; + // -ms-overflow-style: none; -webkit-overflow-scrolling: touch; - overflow: -moz-scrollbars-none; + // overflow: -moz-scrollbars-none; .nav-link { padding-left: .5rem; @@ -29,17 +29,17 @@ } // Hide scrollbars... trying to -::-webkit-scrollbar, -::-webkit-scrollbar-button, -::-webkit-scrollbar-track, -::-webkit-scrollbar-track-piece, -::-webkit-scrollbar-thumb, -::-webkit-scrollbar-corner, -::-webkit-resizer { - display: none; - background: rgba(0,0,0,0); - background-color: rgba(0,0,0,0); -} +// ::-webkit-scrollbar, +// ::-webkit-scrollbar-button, +// ::-webkit-scrollbar-track, +// ::-webkit-scrollbar-track-piece, +// ::-webkit-scrollbar-thumb, +// ::-webkit-scrollbar-corner, +// ::-webkit-resizer { +// // display: none; +// background: rgba(0,0,0,0); +// background-color: rgba(0,0,0,0); +// } .container-outside { &.main-nav { diff --git a/submissions/constants.py b/submissions/constants.py index 5252e752e27ddad562a456d07f9893ef681cef8f..b360f7f6f3c74e164d86266b63dc11c7e165ce91 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -164,7 +164,7 @@ RANKING_CHOICES = ( REPORT_REC = ( (None, '-'), - (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select) NOTE: SELECT NOT YET OPEN, STARTS EARLY 2017'), + (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select)'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), (-1, 'Ask for minor revision'), diff --git a/submissions/forms.py b/submissions/forms.py index ee1365c2b8033b8aca7292f66a0cee41ed5facb5..c13adbe6e338f45fe1a42840c05e0d26ca5ec337 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -187,7 +187,7 @@ class SubmissionChecks: class SubmissionIdentifierForm(SubmissionChecks, forms.Form): - IDENTIFIER_PATTERN_NEW = r'^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$' + IDENTIFIER_PATTERN_NEW = r'^[0-9]{4,}\.[0-9]{4,5}v[0-9]{1,2}$' IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' identifier = forms.RegexField(regex=IDENTIFIER_PATTERN_NEW, strip=True, @@ -818,7 +818,7 @@ class iThenticateReportForm(forms.ModelForm): client = self.client response = client.documents.get(self.document_id) if response['status'] == 200: - return response.get('data')[0].get('documents') + return response.get('data')[0].get('documents')[0] self.add_error(None, "Updating failed. iThenticate didn't return valid data [1]") for msg in client.messages: diff --git a/submissions/migrations/0002_auto_20180118_1033.py b/submissions/migrations/0002_auto_20180118_1033.py new file mode 100644 index 0000000000000000000000000000000000000000..d4e1758aebb750e181a711cbf7cebe74e6a0875f --- /dev/null +++ b/submissions/migrations/0002_auto_20180118_1033.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-01-18 09:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='eicrecommendation', + name='recommendation', + field=models.SmallIntegerField(choices=[(None, '-'), (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select)'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), (-1, 'Ask for minor revision'), (-2, 'Ask for major revision'), (-3, 'Reject')]), + ), + migrations.AlterField( + model_name='report', + name='recommendation', + field=models.SmallIntegerField(choices=[(None, '-'), (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select)'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), (-1, 'Ask for minor revision'), (-2, 'Ask for major revision'), (-3, 'Reject')]), + ), + ]