import datetime
import hashlib
import os
import random
import requests
import string
import xml.etree.ElementTree as ET
from django.conf import settings
#from django.core import serializers
from django.utils import timezone
from django.shortcuts import get_object_or_404, render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.core.files import File
from django.core.mail import EmailMessage
from django.core.urlresolvers import reverse
from django.db import transaction
from django.http import HttpResponse, HttpResponseRedirect
from django.views.decorators.csrf import csrf_protect
from django.db.models import Avg
from .models import *
from .forms import *
from journals.utils import JournalUtils
from submissions.models import SUBMISSION_STATUS_PUBLICLY_UNLISTED
from submissions.models import Submission
from guardian.decorators import permission_required
from guardian.decorators import permission_required_or_403
from guardian.shortcuts import assign_perm
# from requests.adapters import HTTPAdapter
# from requests.packages.urllib3.poolmanager import PoolManager
# import ssl
# class MyAdapter(HTTPAdapter):
# def init_poolmanager(self, connections, maxsize, block=False):
# self.poolmanager = PoolManager(num_pools=connections,
# maxsize=maxsize,
# block=block,
# ssl_version=ssl.PROTOCOL_TLSv1)
############
# Journals
############
# Utilities
# @permission_required('scipost.can_publish_accepted_submission', return_403=True)
# @transaction.atomic
# def open_new_issue(request):
# """
# For a Journal/Volume, creates a new issue.
# """
# settings.JOURNALS_DIR
# + journal_name_abbrev_doi(publication.in_issue.in_volume.in_journal.name)
# + '/' + str(publication.in_issue.in_volume.number)
# + '/' + str(publication.in_issue.number)
def journals(request):
return render(request, 'journals/journals.html')
def scipost_physics(request):
#issues = Issue.objects.filter(
# in_volume__in_journal__name='SciPost Physics').order_by('-until_date')
current_issue = Issue.objects.filter(
in_volume__in_journal__name='SciPost Physics',
start_date__lte=timezone.now(),
until_date__gte=timezone.now()).order_by('-until_date').first()
latest_issue = Issue.objects.filter(
in_volume__in_journal__name='SciPost Physics',
until_date__lte=timezone.now()).order_by('-until_date').first()
#recent_papers = Publication.objects.filter(
# #in_issue=latest_issue).order_by('paper_nr')
# in_issue__in_volume__in_journal__name='SciPost Physics').order_by('-publication_date')[:20]
#accepted_SP_submissions = Submission.objects.filter(
# submitted_to_journal='SciPost Physics', status='accepted'
#).order_by('-latest_activity')
#current_SP_submissions = Submission.objects.filter(
# submitted_to_journal='SciPost Physics'
# ).exclude(status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED
# ).order_by('-submission_date')
context = {
#'issues': issues,
'current_issue': current_issue,
'latest_issue': latest_issue,
#'recent_papers': recent_papers,
#'accepted_SP_submissions': accepted_SP_submissions,
#'current_SP_submissions': current_SP_submissions,
}
return render(request, 'journals/scipost_physics.html', context)
def scipost_physics_issues(request):
issues = Issue.objects.filter(
in_volume__in_journal__name='SciPost Physics').order_by('-until_date')
context = {'issues': issues,}
return render(request, 'journals/scipost_physics_issues.html', context)
[docs]def scipost_physics_recent(request):
"""
Display page for the most recent 20 publications in SciPost Physics.
"""
#latest_issue = Issue.objects.filter(
# in_volume__in_journal__name='SciPost Physics').order_by('-until_date').first()
recent_papers = Publication.objects.filter(
#in_issue=latest_issue).order_by('-publication_date')
in_issue__in_volume__in_journal__name='SciPost Physics').order_by('-publication_date')[:20]
context = {#'latest_issue': latest_issue,
'recent_papers': recent_papers}
return render(request, 'journals/scipost_physics_recent.html', context)
[docs]def scipost_physics_accepted(request):
"""
Display page for submissions to SciPost Physics which
have been accepted but are not yet published.
"""
accepted_SP_submissions = Submission.objects.filter(
submitted_to_journal='SciPost Physics', status='accepted'
).order_by('-latest_activity')
context = {'accepted_SP_submissions': accepted_SP_submissions}
return render(request, 'journals/scipost_physics_accepted.html', context)
# def scipost_physics_submissions(request):
# """
# Display page for submissions to SciPost Physics which
# have been accepted but are not yet published.
# """
# current_SP_submissions = Submission.objects.filter(
# submitted_to_journal='SciPost Physics'
# ).exclude(status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED
# ).order_by('-submission_date')
# context = {'current_SP_submissions': current_SP_submissions}
# return render(request, 'journals/scipost_physics_submissions.html', context)
def scipost_physics_info_for_authors(request):
return render(request, 'journals/scipost_physics_info_for_authors.html')
def scipost_physics_about(request):
return render(request, 'journals/scipost_physics_about.html')
def scipost_physics_issue_detail(request, volume_nr, issue_nr):
issue = get_object_or_404 (Issue, in_volume__in_journal__name='SciPost Physics',
number=issue_nr)
papers = issue.publication_set.order_by('paper_nr')
context = {'issue': issue, 'papers': papers}
return render(request, 'journals/scipost_physics_issue_detail.html', context)
#######################
# Publication process #
#######################
# @permission_required('scipost.can_publish_accepted_submission', return_403=True)
# @transaction.atomic
# def publishing_workspace(request):
# """
# Page containing post-acceptance publishing workflow items.
# """
# accepted_submissions = Submission.objects.filter(status='accepted')
# context = {'accepted_submissions': accepted_submissions,}
# return render(request, 'journals/publishing_workspace.html', context)
[docs]def upload_proofs(request):
"""
TODO
Called by a member of the Production Team.
Upload the production version .pdf of a submission.
"""
return render(request, 'journals/upload_proofs.html')
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
[docs]def initiate_publication(request):
"""
Called by an Editorial Administrator.
Publish the manuscript after proofs have been accepted.
This method prefills a ValidatePublicationForm for further
processing (verification in validate_publication method).
"""
if request.method == 'POST':
initiate_publication_form = InitiatePublicationForm(request.POST)
if initiate_publication_form.is_valid():
submission = get_object_or_404(Submission,
pk=initiate_publication_form.cleaned_data['accepted_submission'].id)
current_issue = get_object_or_404(Issue,
pk=initiate_publication_form.cleaned_data['to_be_issued_in'].id)
# Determine next available paper number:
papers_in_current_issue = Publication.objects.filter(in_issue=current_issue)
paper_nr = 1
while papers_in_current_issue.filter(paper_nr=paper_nr).exists():
paper_nr += 1
if paper_nr > 999:
raise PaperNumberingError(paper_nr)
doi_label = (
journal_name_abbrev_doi(current_issue.in_volume.in_journal.name)
+ '.' + str(current_issue.in_volume.number)
+ '.' + str(current_issue.number) + '.' + paper_nr_string(paper_nr)
)
doi_string='10.21468/' + doi_label
BiBTeX_entry = (
'@Article{' + doi_label + ',\n'
'\ttitle={{' + submission.title + '}},\n'
'\tauthor={' + submission.author_list.replace(',', ' and') + '},\n'
'\tjournal={'
+ journal_name_abbrev_citation(current_issue.in_volume.in_journal.name)
+ '},\n'
'\tvolume={' + str(current_issue.in_volume.number) + '},\n'
'\tissue={' + str(current_issue.number) + '},\n'
'\tpages={' + paper_nr_string(paper_nr) + '},\n'
'\tyear={' + current_issue.until_date.strftime('%Y') + '},\n'
'\tpublisher={SciPost},\n'
'\tdoi={' + doi_string + '},\n'
'\turl={https://scipost.org/' + doi_string + '},\n'
'}\n'
)
initial = {
'accepted_submission': submission,
'in_issue': current_issue,
'paper_nr': paper_nr,
'discipline': submission.discipline,
'domain': submission.domain,
'subject_area': submission.subject_area,
'secondary_areas': submission.secondary_areas,
'title': submission.title,
'author_list': submission.author_list,
'abstract': submission.abstract,
'BiBTeX_entry': BiBTeX_entry,
'doi_label': doi_label,
'doi_string': doi_string,
'submission_date': initiate_publication_form.cleaned_data['original_submission_date'],
'acceptance_date': initiate_publication_form.cleaned_data['acceptance_date'],
'publication_date': timezone.now(),
'latest_activity': timezone.now(),
}
validate_publication_form = ValidatePublicationForm(initial=initial)
context = {'validate_publication_form': validate_publication_form,}
return render(request, 'journals/validate_publication.html', context)
else:
errormessage = 'The form was not filled validly.'
context = {'initiate_publication_form': initiate_publication_form,
'errormessage': errormessage}
return render(request, 'journals/initiate_publication.html', context)
else:
initiate_publication_form = InitiatePublicationForm()
context = {'initiate_publication_form': initiate_publication_form}
return render(request, 'journals/initiate_publication.html', context)
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
[docs]def validate_publication(request):
"""
This creates a Publication instance from the ValidatePublicationForm,
pre-filled by the initiate_publication method above.
"""
# TODO: move from uploads to Journal folder
# TODO: create metadata
# TODO: set DOI, register with Crossref
# TODO: add funding info
if request.method == 'POST':
validate_publication_form = ValidatePublicationForm(request.POST, request.FILES)
if validate_publication_form.is_valid():
publication = validate_publication_form.save()
# Fill in remaining data
publication.pdf_file = request.FILES['pdf_file']
submission = publication.accepted_submission
publication.authors.add(*submission.authors.all())
publication.authors_claims.add(*submission.authors_claims.all())
publication.authors_false_claims.add(*submission.authors_false_claims.all())
publication.save()
# Move file to final location
initial_path = publication.pdf_file.path
new_dir = (publication.in_issue.path
+ paper_nr_string(publication.paper_nr))
new_path = new_dir + '/' + publication.doi_label.replace('.', '_') + '.pdf'
os.makedirs(new_dir)
os.rename(initial_path, new_path)
publication.pdf_file.name = new_path
publication.save()
# Mark the submission as having been published:
publication.accepted_submission.published_as = publication
publication.accepted_submission.status = 'published'
publication.accepted_submission.save()
# TODO: Create a Commentary Page
# Email authors
JournalUtils.load({'publication': publication})
JournalUtils.send_authors_paper_published_email()
ack_header = 'The publication has been validated.'
context = {'ack_header': ack_header,}
return render(request, 'scipost/acknowledgement.html', context)
else:
errormessage = 'The form was invalid.'
context = {'publication': publication,
'validate_publication_form': validate_publication_form,
'errormessage': errormessage}
return render(request, 'journals/validate_publication.html', context)
else:
validate_publication_form = ValidatePublicationForm()
context = {'validate_publication_form': validate_publication_form}
return render(request, 'journals/validate_publication.html', context)
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
def mark_first_author(request, publication_id, contributor_id):
publication = get_object_or_404(Publication, id=publication_id)
contributor = get_object_or_404(Contributor, id=contributor_id)
publication.first_author = contributor
publication.first_author_unregistered = None
publication.save()
return redirect(reverse('scipost:publication_detail',
kwargs={'doi_string': publication.doi_string,}))
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
def mark_first_author_unregistered(request, publication_id, unregistered_author_id):
publication = get_object_or_404(Publication, id=publication_id)
unregistered_author = get_object_or_404(UnregisteredAuthor, id=unregistered_author_id)
publication.first_author = None
publication.first_author_unregistered = unregistered_author
publication.save()
return redirect(reverse('scipost:publication_detail',
kwargs={'doi_string': publication.doi_string,}))
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
[docs]def add_author(request, publication_id, contributor_id=None, unregistered_author_id=None):
"""
If not all authors are registered Contributors or if they have not
all claimed authorship, this method allows editorial administrators
to associated them to the publication.
This is important for the Crossref metadata, in which all authors must appear.
"""
publication = get_object_or_404(Publication, id=publication_id)
if contributor_id:
contributor = get_object_or_404(Contributor, id=contributor_id)
publication.authors.add(contributor)
publication.save()
return redirect(reverse('scipost:publication_detail',
kwargs={'doi_string': publication.doi_string,}))
if request.method == 'POST':
form = UnregisteredAuthorForm(request.POST)
if form.is_valid():
contributors_found = Contributor.objects.filter(
user__last_name__icontains=form.cleaned_data['last_name'])
unregistered_authors_found = UnregisteredAuthor.objects.filter(
last_name__icontains=form.cleaned_data['last_name'])
new_unreg_author_form = UnregisteredAuthorForm(
initial={'first_name': form.cleaned_data['first_name'],
'last_name': form.cleaned_data['last_name'],})
else:
errormessage = 'Please fill in the form properly'
return render(request, 'scipost/error.html', context={'errormessage': errormessage})
else:
form = UnregisteredAuthorForm()
contributors_found = None
unregistered_authors_found = None
new_unreg_author_form = UnregisteredAuthorForm()
context = {'publication': publication,
'contributors_found': contributors_found,
'unregistered_authors_found': unregistered_authors_found,
'form': form,
'new_unreg_author_form': new_unreg_author_form,}
return render(request, 'journals/add_author.html', context)
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
def add_unregistered_author(request, publication_id, unregistered_author_id):
publication = get_object_or_404(Publication, id=publication_id)
unregistered_author = get_object_or_404(UnregisteredAuthor, id=unregistered_author_id)
publication.unregistered_authors.add(unregistered_author)
publication.save()
return redirect(reverse('scipost:publication_detail',
kwargs={'doi_string': publication.doi_string,}))
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
def add_new_unreg_author(request, publication_id):
publication = get_object_or_404(Publication, id=publication_id)
if request.method == 'POST':
new_unreg_author_form = UnregisteredAuthorForm(request.POST)
if new_unreg_author_form.is_valid():
new_unreg_author = UnregisteredAuthor(
first_name = new_unreg_author_form.cleaned_data['first_name'],
last_name = new_unreg_author_form.cleaned_data['last_name'],)
new_unreg_author.save()
publication.authors_unregistered.add(new_unreg_author)
return redirect(reverse('scipost:publication_detail',
kwargs={'doi_string': publication.doi_string,}))
errormessage = 'Method add_new_unreg_author can only be called with POST.'
return render(request, 'scipost/error.html', context={'errormessage': errormessage})
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
# @permission_required('scipost.can_publish_accepted_submission', return_403=True)
# @transaction.atomic
# def test_metadata_xml_deposit(request, doi_string):
# """
# Prior to the actual Crossref metadata deposit,
# test the metadata_xml using the Crossref test server.
# Makes use of the python requests module.
# """
# publication = get_object_or_404 (Publication, doi_string=doi_string)
# url = 'http://test.crossref.org/servlet/deposit'
# #headers = {'Content-type': 'multipart/form-data'}
# 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', {'Expires': '0'})}
# files = {'fname': ('metadata.xml', publication.metadata_xml, 'multipart/form-data')}
# r = requests.post(url,
# params=params,
# files=files,
# #verify=settings.CERTFILE,
# #verify=False,
# )
# #s = requests.Session()
# #s.mount('https://', MyAdapter())
# #r = s.post(url, params=params, files=files)
# response_headers = r.headers
# response_text = r.text
# context = {'publication': publication,
# 'response_headers': response_headers,
# 'response_text': response_text,
# }
# return render(request, 'journals/test_metadata_xml_deposit.html', context)
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
@permission_required('scipost.can_publish_accepted_submission', return_403=True)
@transaction.atomic
def harvest_citedby_links(request, doi_string):
publication = get_object_or_404(Publication, doi_string=doi_string)
# create a doi_batch_id
salt = ""
for i in range(5):
salt = salt + random.choice(string.ascii_letters)
salt = salt.encode('utf8')
idsalt = publication.title[:10]
idsalt = idsalt.encode('utf8')
doi_batch_id = hashlib.sha1(salt+idsalt).hexdigest()
query_xml = ('<?xml version = "1.0" encoding="UTF-8"?>'
'<query_batch version="2.0" xmlns = "http://www.crossref.org/qschema/2.0"'
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
'xsi:schemaLocation="http://www.crossref.org/qschema/2.0 '
'http://www.crossref.org/qschema/crossref_query_input2.0.xsd">'
'<head>'
'<email_address>admin@scipost.org</email_address>'
'<doi_batch_id>' + str(doi_batch_id) + '</doi_batch_id>'
'</head>'
'<body>'
'<fl_query alert="false">'
'<doi>' + publication.doi_string + '</doi>'
'</fl_query>'
'</body>'
'</query_batch>')
url = 'http://doi.crossref.org/servlet/getForwardLinks'
params = {'usr': settings.CROSSREF_LOGIN_ID,
'pwd': settings.CROSSREF_LOGIN_PASSWORD,
'qdata': query_xml,
'doi': publication.doi_string,}
r = requests.post(url, params=params,)
response_headers = r.headers
#response_text = bytes(r.text, 'utf-8').decode('unicode_escape')
response_text = r.text
response_deserialized = ET.fromstring(r.text)
prefix = '{http://www.crossref.org/qrschema/2.0}'
citations = []
for link in response_deserialized.iter(prefix + 'forward_link'):
doi = link.find(prefix + 'journal_cite').find(prefix + 'doi').text
article_title = link.find(prefix + 'journal_cite').find(prefix + 'article_title').text
journal_abbreviation = link.find(prefix + 'journal_cite').find(
prefix + 'journal_abbreviation').text
volume = link.find(prefix + 'journal_cite').find(prefix + 'volume').text
try:
first_page = link.find(prefix + 'journal_cite').find(prefix + 'first_page').text
except:
first_page = None
try:
item_number = link.find(prefix + 'journal_cite').find(prefix + 'item_number').text
except:
item_number = None
for author in link.find(prefix + 'journal_cite').find(
prefix + 'contributors').iter(prefix + 'contributor'):
if author.get('sequence') == 'first':
first_author_given_name = author.find(prefix + 'given_name').text
first_author_surname = author.find(prefix + 'surname').text
else:
multiauthors = True
year = link.find(prefix + 'journal_cite').find(prefix + 'year').text
citations.append({'doi': doi,
'article_title': article_title,
'journal_abbreviation': journal_abbreviation,
'first_author_given_name': first_author_given_name,
'first_author_surname': first_author_surname,
'multiauthors': multiauthors,
'volume': volume,
'first_page': first_page,
'item_number': item_number,
'year': year,})
publication.citedby = citations
publication.save()
context = {'publication': publication,
'response_headers': response_headers,
'response_text': response_text,
'response_deserialized': response_deserialized,
'citations': citations,
}
return render(request, 'journals/harvest_citedby_links.html', context)
###########
# Viewing #
###########
def publication_detail(request, doi_string):
publication = get_object_or_404 (Publication, doi_string=doi_string)
context = {'publication': publication,}
return render(request, 'journals/publication_detail.html', context)
def publication_pdf(request, doi_string):
publication = get_object_or_404 (Publication, doi_string=doi_string)
pdf = File(publication.pdf_file)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = ('filename='
+ publication.doi_label.replace('.', '_') + '.pdf')
return response
def publication_detail_from_doi_label(request, doi_label):
publication = get_object_or_404 (Publication, doi_label=doi_label)
context = {'publication': publication,}
return render(request, 'journals/publication_detail.html', context)
def publication_pdf_from_doi_label(request, doi_label):
publication = get_object_or_404 (Publication, doi_label=doi_label)
pdf = File(publication.pdf_file)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = ('filename='
+ publication.doi_label.replace('.', '_') + '.pdf')
return response