diff --git a/profiles/forms.py b/profiles/forms.py index ab7d53a9ee5083b0fcf816e52167303e843d2662..92862facaf33f881125b393d8c35e6e0dd2a1da1 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -36,7 +36,8 @@ class ProfileForm(forms.ModelForm): def clean_email(self): """Check that the email isn't yet associated to an existing Profile.""" cleaned_email = self.cleaned_data['email'] - if ProfileEmail.objects.filter(email=cleaned_email).exclude(profile__id=self.instance.id).exists(): + if ProfileEmail.objects.filter( + email=cleaned_email).exclude(profile__id=self.instance.id).exists(): raise forms.ValidationError('A Profile with this email already exists.') return cleaned_email @@ -69,6 +70,20 @@ class ProfileForm(forms.ModelForm): return profile +class SimpleProfileForm(ProfileForm): + """ + Simple version of ProfileForm, displaying only required fields. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['expertises'].widget = forms.HiddenInput() + self.fields['orcid_id'].widget = forms.HiddenInput() + self.fields['webpage'].widget = forms.HiddenInput() + self.fields['accepts_SciPost_emails'].widget = forms.HiddenInput() + self.fields['accepts_refereeing_requests'].widget = forms.HiddenInput() + + class ModelChoiceFieldwithid(forms.ModelChoiceField): def label_from_instance(self, obj): return '%s (id = %i)' % (super().label_from_instance(obj), obj.id) diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index a6be4d4a022428bfb91019056a2879531f8e74ad..1d13fcd2a8d05b9209234c34155c754de9421a79 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -402,6 +402,7 @@ class Command(BaseCommand): EditorialCollege.permissions.set([ can_view_pool, can_take_charge_of_submissions, + can_create_profiles, can_attend_VGMs, can_view_statistics, can_manage_ontology, diff --git a/submissions/forms.py b/submissions/forms.py index dd0bbe9198598b04e6ac87f839c3c485d1a311dd..5a6b21b3cf061da4dc58fed5ead1ba55ecc9327b 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -811,83 +811,7 @@ class ConsiderAssignmentForm(forms.Form): class RefereeSearchForm(forms.Form): last_name = forms.CharField(widget=forms.TextInput({ - 'placeholder': 'Search for a referee in the SciPost database'})) - - -class RefereeSelectForm(forms.Form): - """Pre-fill form to get the last name of the requested referee.""" - - last_name = forms.CharField(widget=forms.TextInput({ - 'placeholder': 'Search in contributors database'})) - - -class RefereeRecruitmentForm(forms.ModelForm): - """Invite non-registered scientist to register and referee a Submission.""" - - class Meta: - model = RefereeInvitation - fields = [ - 'profile', - 'title', - 'first_name', - 'last_name', - 'email_address', - 'auto_reminders_allowed', - 'invitation_key'] - widgets = { - 'profile': forms.HiddenInput(), - 'invitation_key': forms.HiddenInput() - } - - def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) - self.submission = kwargs.pop('submission', None) - - initial = kwargs.pop('initial', {}) - initial['invitation_key'] = get_new_secrets_key() - kwargs['initial'] = initial - super().__init__(*args, **kwargs) - - def clean_email_address(self): - email = self.cleaned_data['email_address'] - if Contributor.objects.filter(user__email=email).exists(): - contr = Contributor.objects.get(user__email=email) - msg = ( - 'This email address is already registered. ' - 'Invite {title} {last_name} using the link above.') - self.add_error('email_address', msg.format( - title=contr.get_title_display(), last_name=contr.user.last_name)) - return email - - def save(self, commit=True): - if not self.request or not self.submission: - raise forms.ValidationError('No request or Submission given.') - - # Try to associate an existing Profile to ref/reg invitations: - profile = Profile.objects.get_unique_from_email_or_None( - email=self.cleaned_data['email_address']) - self.instance.profile = profile - - self.instance.submission = self.submission - self.instance.invited_by = self.request.user.contributor - referee_invitation = super().save(commit=False) - - registration_invitation = RegistrationInvitation( - profile=profile, - title=referee_invitation.title, - first_name=referee_invitation.first_name, - last_name=referee_invitation.last_name, - email=referee_invitation.email_address, - invitation_type=INVITATION_REFEREEING, - created_by=self.request.user, - invited_by=self.request.user, - invitation_key=referee_invitation.invitation_key, - key_expires=timezone.now() + datetime.timedelta(days=365)) - - if commit: - referee_invitation.save() - registration_invitation.save() - return (referee_invitation, registration_invitation) + 'placeholder': 'Search for a referee in the SciPost Profiles database'})) class ConsiderRefereeInvitationForm(forms.Form): diff --git a/submissions/templates/submissions/referee_form.html b/submissions/templates/submissions/select_referee.html similarity index 57% rename from submissions/templates/submissions/referee_form.html rename to submissions/templates/submissions/select_referee.html index 75ce7fa3fe3b474b62303c4b7997e4031bb81729..87a3caeadac29f1cc839c4f6229648b17fc54a09 100644 --- a/submissions/templates/submissions/referee_form.html +++ b/submissions/templates/submissions/select_referee.html @@ -41,10 +41,9 @@ <div class="col-12"> <h2 class="highlight" id="form">Select an additional Referee</h2> - <form action="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}#form" method="post"> - {% csrf_token %} - {{ ref_search_form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Find referee"> + <form action="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="get"> + {{ referee_search_form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Find referee"> </form> </div> </div> @@ -85,7 +84,7 @@ <tr> <td>{{ profile }}</td> <td>{% if profile.contributor %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> - <td>Send refereeing invitation <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=1 %}">with</a> or <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=0 %}">without</a> {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %}</td> + <td>Send refereeing invitation <a href="{% url 'submissions:invite_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=1 %}">with</a> or <a href="{% url 'submissions:invite_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=0 %}">without</a> auto-reminders {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %}</td> </tr> {% empty %} <tr> @@ -96,36 +95,21 @@ </table> {% endif %} - {% if contributors_found %} - <h3>Identified as contributor:</h3> - <table class="table"> - {% for contributor in contributors_found %} - <tr> - <td>{{ contributor.user.first_name }} {{ contributor.user.last_name }}</td> - <td> - <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr contributor_id=contributor.id auto_reminders_allowed=1 %}">Send refereeing invitation with</a> or <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr contributor_id=contributor.id auto_reminders_allowed=0 %}">without</a> auto reminders - {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %} - </td> - </tr> - {% endfor %} - </table> - {% elif ref_search_form.has_changed %} - <p>No Contributor with this last name could be identified.</p> - {% endif %} </div> </div> -{% if ref_recruit_form %} - <div class="row"> - <div class="col-12"> - <h3 class="mb-2">If the referee you were looking for was not found by using the method above, you can send a registration and refereeing invitation by filling this form:</h3> - <form action="{% url 'submissions:recruit_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> - {% csrf_token %} - {{ ref_recruit_form|bootstrap }} - <input type="submit" name="submit" value="Send invitation" class="btn btn-primary"> - </form> - </div> - </div> +{% if profile_form %} +<div class="row"> + <div class="col-12"> + <h3 class="mb-2">If the referee you were looking for was not found by using the method above, you can define a new Profile by filling this form:</h3> + <form action="{% url 'submissions:add_referee_profile' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + {% csrf_token %} + {{ profile_form|bootstrap }} + <input type="submit" name="submit" value="Create Profile" class="btn btn-primary"> + </form> + <h4>(you will be taken back to this page, and the newly-created Profile will appear in the list above, from which you can then invite this referee)</h4> + </div> +</div> {% endif %} diff --git a/submissions/urls.py b/submissions/urls.py index 198a4cbcf8fd5841ef6a3fb7093a1ad43e632620..9b2f9985cbf8c22e4f8c9bd2600efb1c94925991 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -100,22 +100,14 @@ urlpatterns = [ url(r'^assignments$', views.assignments, name='assignments'), url(r'^editorial_page/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.editorial_page, name='editorial_page'), - url(r'^find_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), - views.find_referee, name='find_referee'), - url(r'^invite_referee/{regex}/(?P<profile_id>[0-9]+)' - '/(?P<auto_reminders_allowed>[0-1])$'.format( - regex=SUBMISSIONS_COMPLETE_REGEX), - views.invite_referee, name='invite_referee'), - # The following 3 views to be deprecated: url(r'^select_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.select_referee, name='select_referee'), - url(r'^recruit_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), - views.recruit_referee, name='recruit_referee'), - url(r'^send_refereeing_invitation/{regex}/(?P<contributor_id>[0-9]+)' + url(r'^add_referee_profile/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + views.add_referee_profile, name='add_referee_profile'), + url(r'^invite_referee/{regex}/(?P<profile_id>[0-9]+)' '/(?P<auto_reminders_allowed>[0-1])$'.format( regex=SUBMISSIONS_COMPLETE_REGEX), - views.send_refereeing_invitation, name='send_refereeing_invitation'), - # above 3 views to be deprecated + views.invite_referee, name='invite_referee'), url(r'^set_refinv_auto_reminder/(?P<invitation_id>[0-9]+)/(?P<auto_reminders>[0-1])$', views.set_refinv_auto_reminder, name='set_refinv_auto_reminder'), url(r'^accept_or_decline_ref_invitations/$', diff --git a/submissions/views.py b/submissions/views.py index b24523515f2e47ff9c92b9ff88043866efb3a82b..0d9cfec0f8144212b5707a151ace60266416b958 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -33,9 +33,10 @@ from .mixins import SubmissionAdminViewMixin from .forms import ( SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm, RecommendationVoteForm, ConsiderAssignmentForm, InviteEditorialAssignmentForm, EditorialAssignmentForm, VetReportForm, - SetRefereeingDeadlineForm, RefereeSearchForm, RefereeSelectForm, + SetRefereeingDeadlineForm, RefereeSearchForm, #RefereeSelectForm, iThenticateReportForm, VotingEligibilityForm, - RefereeRecruitmentForm, ConsiderRefereeInvitationForm, EditorialCommunicationForm, ReportForm, + #RefereeRecruitmentForm, + ConsiderRefereeInvitationForm, EditorialCommunicationForm, ReportForm, SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm, EICRecommendationForm, SubmissionPoolFilterForm, FixCollegeDecisionForm, SubmissionPrescreeningForm, PreassignEditorsFormSet, SubmissionReassignmentForm) @@ -43,15 +44,21 @@ from .utils import SubmissionUtils from colleges.permissions import fellowship_required, fellowship_or_admin_required from comments.forms import CommentForm +from common.helpers import get_new_secrets_key +from invitations.constants import STATUS_SENT +from invitations.models import RegistrationInvitation from journals.models import Journal from mails.views import MailEditingSubView from ontology.models import Topic from ontology.forms import SelectTopicForm from production.forms import ProofsDecisionForm from profiles.models import Profile +from profiles.forms import SimpleProfileForm +from scipost.constants import INVITATION_REFEREEING from scipost.forms import RemarkForm from scipost.mixins import PaginationMixin from scipost.models import Contributor, Remark +from submissions.models import RefereeInvitation # from notifications.views import is_test_user # Temporarily until release @@ -858,7 +865,7 @@ def cycle_form_submit(request, identifier_w_vn_nr): @login_required @fellowship_or_admin_required() -def find_referee(request, identifier_w_vn_nr): +def select_referee(request, identifier_w_vn_nr): """ Search for a referee in the set of Profiles, and if none is found, create a new Profile and return to this page for further processing. @@ -867,10 +874,10 @@ def find_referee(request, identifier_w_vn_nr): preprint__identifier_w_vn_nr=identifier_w_vn_nr) context = {} queryresults = '' - referee_search_form = RefereeSearchForm(request.POST or None) + referee_search_form = RefereeSearchForm(request.GET or None) if referee_search_form.is_valid(): profiles_found = Profile.objects.filter( - last_name__icontains=ref_search_form.cleaned_data['last_name']) + last_name__icontains=referee_search_form.cleaned_data['last_name']) context['profiles_found'] = profiles_found # Check for recent co-authorship (thus referee disqualification) try: @@ -880,7 +887,7 @@ def find_referee(request, identifier_w_vn_nr): for author in submission.metadata['entries'][0]['authors'][1:]: sub_auth_boolean_str += '+OR+' + author['name'].split()[-1] sub_auth_boolean_str += ')+AND+' - search_str = sub_auth_boolean_str + ref_search_form.cleaned_data['last_name'] + ')' + search_str = sub_auth_boolean_str + referee_search_form.cleaned_data['last_name'] + ')' queryurl = ('https://export.arxiv.org/api/query?search_query=au:%s' % search_str + '&sortBy=submittedDate&sortOrder=descending' '&max_results=5') @@ -888,157 +895,104 @@ def find_referee(request, identifier_w_vn_nr): queryresults = arxivquery except KeyError: pass - context['ref_recruit_form'] = RefereeRecruitmentForm() - - -# The coming 3 methods are to be deprecated -@login_required -@fellowship_or_admin_required() -def select_referee(request, identifier_w_vn_nr): - """Invite scientist to referee a Submission. - - Accessible for: Editor-in-charge and Editorial Administration. - It'll list possible already registered Contributors that match the search. If the scientist - is not yet registered, he will be invited using a RegistrationInvitation as well. In - addition the page will show possible conflicts of interests, with that information - coming from the ArXiv API. - """ - submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), - preprint__identifier_w_vn_nr=identifier_w_vn_nr) - context = {} - queryresults = '' - ref_search_form = RefereeSelectForm(request.POST or None) - if ref_search_form.is_valid(): - contributors_found = Contributor.objects.filter( - user__last_name__icontains=ref_search_form.cleaned_data['last_name']) - context['contributors_found'] = contributors_found - # Check for recent co-authorship (thus referee disqualification) - try: - sub_auth_boolean_str = '((' + (submission - .metadata['entries'][0]['authors'][0]['name'] - .split()[-1]) - for author in submission.metadata['entries'][0]['authors'][1:]: - sub_auth_boolean_str += '+OR+' + author['name'].split()[-1] - sub_auth_boolean_str += ')+AND+' - search_str = sub_auth_boolean_str + ref_search_form.cleaned_data['last_name'] + ')' - queryurl = ('https://export.arxiv.org/api/query?search_query=au:%s' - % search_str + '&sortBy=submittedDate&sortOrder=descending' - '&max_results=5') - arxivquery = feedparser.parse(queryurl) - queryresults = arxivquery - except KeyError: - pass - context['ref_recruit_form'] = RefereeRecruitmentForm() - + context['profile_form'] = SimpleProfileForm() context.update({ 'submission': submission, - 'ref_search_form': ref_search_form, - 'queryresults': queryresults + 'referee_search_form': referee_search_form, + 'queryresults': queryresults, }) - return render(request, 'submissions/referee_form.html', context) + return render(request, 'submissions/select_referee.html', context) @login_required @fellowship_or_admin_required() @transaction.atomic -def recruit_referee(request, identifier_w_vn_nr): - """Invite a non-registered scientist to register and referee a Submission. - - Accessible for: Editor-in-charge and Editorial Administration - If the Editor-in-charge does not find the desired referee among Contributors - (otherwise, the method send_refereeing_invitation is used), he/she can invite somebody - by providing name + contact details. This function emails a registration invitation to this - person. The pending refereeing invitation is then recognized upon registration, using the - invitation token. - """ +def add_referee_profile(request, identifier_w_vn_nr): submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - - if request.method == 'GET': - # This leads to unexpected 500 errors - return redirect(reverse('submissions:select_referee', args=(identifier_w_vn_nr,))) - - ref_recruit_form = RefereeRecruitmentForm( - request.POST or None, request=request, submission=submission) - if ref_recruit_form.is_valid(): - referee_invitation, registration_invitation = ref_recruit_form.save(commit=False) - mail_request = MailEditingSubView(request, - mail_code='referees/invite_unregistered_to_referee', - instance=referee_invitation) - mail_request.add_form(ref_recruit_form) - if mail_request.is_valid(): - referee_invitation.save() - registration_invitation.save() - - messages.success(request, 'Referee {} invited'.format( - registration_invitation.last_name)) - submission.add_event_for_author('A referee has been invited.') - submission.add_event_for_eic('{} has been recruited and invited as a referee.'.format( - referee_invitation.last_name)) - - mail_request.send() - return redirect(reverse('submissions:editorial_page', - kwargs={'identifier_w_vn_nr': identifier_w_vn_nr})) - else: - return mail_request.return_render() - - ref_search_form = RefereeSelectForm(request.POST or None) - contributors_found = Contributor.objects.filter( - user__email=ref_recruit_form.cleaned_data['email_address']) - context = { - 'ref_recruit_form': ref_recruit_form, - 'ref_search_form': ref_search_form, - 'submission': submission, - 'queryresults': [], - 'contributors_found': contributors_found, - } - return render(request, 'submissions/referee_form.html', context) + profile_form = SimpleProfileForm(request.POST or None) + if profile_form.is_valid(): + profile_form.save() + messages.success(request, + 'Profile added, you can now invite this referee using the links above') + else: + messages.error(request, 'Could not create this Profile') + for error_messages in profile_form.errors.values(): + messages.warning(request, *error_messages) + return redirect(reverse( + 'submissions:select_referee', + kwargs={'identifier_w_vn_nr': submission.preprint.identifier_w_vn_nr}) + + ('?last_name=%s' % profile_form.cleaned_data['last_name'])) @login_required @fellowship_or_admin_required() @transaction.atomic -def send_refereeing_invitation(request, identifier_w_vn_nr, contributor_id, - auto_reminders_allowed): +def invite_referee(request, identifier_w_vn_nr, profile_id, auto_reminders_allowed): """ - Send RefereeInvitation to a registered Contributor. - - This method is called by the EIC from the submission's editorial_page, - in the case where the referee is an identified Contributor. - For a referee who isn't a Contributor yet, the method recruit_referee above - is called instead. - - Accessible for: Editor-in-charge and Editorial Administration. + Invite a referee linked to a Profile. + If the Profile has a Contributor object, a simple invitation is sent. + If there is no associated Contributor, a registration invitation is included. """ submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - contributor = get_object_or_404(Contributor, pk=contributor_id) - profile = Profile.objects.get_unique_from_email_or_None(contributor.user.email) + profile = get_object_or_404(Profile, pk=profile_id) - if not contributor.is_currently_available: - errormessage = ('This Contributor is marked as currently unavailable. ' - 'Please go back and select another referee.') - return render(request, 'scipost/error.html', {'errormessage': errormessage}) + contributor = None + if hasattr(profile, 'contributor') and profile.contributor: + contributor = profile.contributor - invitation = RefereeInvitation( + referee_invitation, created = RefereeInvitation.objects.get_or_create( profile=profile, - submission=submission, referee=contributor, - title=contributor.title, - first_name=contributor.user.first_name, - last_name=contributor.user.last_name, - email_address=contributor.user.email, + submission=submission, + title=profile.title, + first_name=profile.first_name, + last_name=profile.last_name, + email_address=profile.email, auto_reminders_allowed=auto_reminders_allowed, - # the key is only used for inviting unregistered users - date_invited=timezone.now(), invited_by=request.user.contributor) - mail_request = MailEditingSubView(request, mail_code='referees/invite_contributor_to_referee', - invitation=invitation) + key = '' + if created: + key = get_new_secrets_key() + referee_invitation.invitation_key = key + referee_invitation.save() + + registration_invitation = None + if contributor: + if not profile.contributor.is_currently_available: + errormessage = ('This Contributor is marked as currently unavailable. ' + 'Please go back and select another referee.') + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + mail_request = MailEditingSubView(request, + mail_code='referees/invite_contributor_to_referee', + invitation=referee_invitation) + else: # no Contributor, so registration invitation + registration_invitation, reginv_created = RegistrationInvitation.objects.get_or_create( + profile=profile, + title=profile.title, + first_name=profile.first_name, + last_name=profile.last_name, + email=profile.email, + invitation_type=INVITATION_REFEREEING, + created_by=request.user, + invited_by=request.user, + invitation_key=referee_invitation.invitation_key) + mail_request = MailEditingSubView(request, + mail_code='referees/invite_unregistered_to_referee', + invitation=referee_invitation) + if mail_request.is_valid(): - invitation.save() + referee_invitation.date_invited = timezone.now() + referee_invitation.save() + if registration_invitation: + registration_invitation.status = STATUS_SENT + registration_invitation.key_expires = timezone.now() + datetime.timedelta(days=365) + registration_invitation.save() submission.add_event_for_author('A referee has been invited.') - submission.add_event_for_eic('Referee %s has been invited.' % contributor.user.last_name) + submission.add_event_for_eic('Referee %s has been invited.' % profile.last_name) messages.success(request, 'Invitation sent') mail_request.send() return redirect(reverse('submissions:editorial_page',