SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 81be6382 authored by George Katsikas's avatar George Katsikas :goat:
Browse files

add assignment offer form to submissions pool

fixes #337
parent 2831a35c
No related branches found
No related tags found
No related merge requests found
......@@ -5,10 +5,11 @@ __license__ = "AGPL v3"
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div, Field, HTML, ButtonHolder, Button
from crispy_forms.layout import Layout, Div, Field
from crispy_bootstrap5.bootstrap5 import FloatingField
from ethics.models import SubmissionClearance
from submissions.models.assignment import ConditionalAssignmentOffer
from ..models import Qualification, Readiness
......@@ -100,7 +101,8 @@ class RadioAppraisalForm(forms.Form):
label="Readiness",
choices=(("assign_now", "Ready to take charge now"),)
+ Readiness.STATUS_CHOICES[0:1]
+ Readiness.STATUS_CHOICES[4:5],
+ Readiness.STATUS_CHOICES[3:4]
+ Readiness.STATUS_CHOICES[5:6],
widget=forms.RadioSelect(),
required=False,
initial=None,
......@@ -140,6 +142,12 @@ class RadioAppraisalForm(forms.Form):
self.initial["readiness"] = readiness.status
# Disable the readiness field if the fellow made an assignment offer
if self.submission.conditional_assignment_offers.filter(
offered_by=self.fellow.contributor
).exists():
self.fields["readiness"].disabled = True
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
......@@ -169,6 +177,8 @@ class RadioAppraisalForm(forms.Form):
action = "take charge"
elif readiness == "desk_reject":
action = "suggest a desk rejection"
elif readiness == Readiness.STATUS_CONDITIONAL:
action = "offer a conditional assignment"
if action: # If readiness is set to assign_now or desk_reject
if reason: # Both readiness and expertise_level are required
......@@ -186,10 +196,8 @@ class RadioAppraisalForm(forms.Form):
qualification, _ = Qualification.objects.get_or_create(
submission=self.submission, fellow=self.fellow
)
print(expertise_level)
qualification.expertise_level = expertise_level
qualification.save()
print(qualification)
if (
readiness_status := self.cleaned_data["readiness"]
......@@ -197,10 +205,8 @@ class RadioAppraisalForm(forms.Form):
readiness, _ = Readiness.objects.get_or_create(
submission=self.submission, fellow=self.fellow
)
print(readiness_status)
readiness.status = readiness_status
readiness.save()
print(readiness)
@property
def is_qualified(self):
......@@ -235,3 +241,104 @@ class RadioAppraisalForm(forms.Form):
"""
is_ready_to_take_charge = self.cleaned_data["readiness"] == "assign_now"
return is_ready_to_take_charge and self.is_qualified and self.has_clearance
class ConditionalAssignmentOfferInlineForm(forms.ModelForm):
class Meta:
model = ConditionalAssignmentOffer
fields = ["submission", "offered_by", "condition_type"]
widgets = {
"submission": forms.HiddenInput(),
"offered_by": forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
self.submission = kwargs.pop("submission")
self.offered_by = kwargs.pop("offered_by")
self.readonly = kwargs.pop("readonly", False)
# Create field depending on the type of condition
extra_fields = {}
condition_type = None
if args and (data := args[0]) and data.get("condition_type"):
condition_type = data.get("condition_type")
elif instance := kwargs.get("instance"):
condition_type = instance.condition_type
elif len(ConditionalAssignmentOffer.CONDITION_CHOICES) == 1:
condition_type = ConditionalAssignmentOffer.CONDITION_CHOICES[0][0]
if condition_type == "JournalTransfer":
alternative_journal_id = forms.ModelChoiceField(
label="Alternative journal",
queryset=self.submission.submitted_to.alternative_journals.all(),
)
extra_fields["alternative_journal_id"] = alternative_journal_id
self.base_fields.update(extra_fields)
super().__init__(*args, **kwargs)
self.initial["submission"] = self.submission
self.initial["offered_by"] = self.offered_by
self.fields["condition_type"].label = "Condition for assignment"
self.fields["condition_type"].choices = (
ConditionalAssignmentOffer.CONDITION_CHOICES
)
# If the form is readonly, disable all fields
for field in self.fields:
if self.readonly:
self.fields[field].disabled = True
if field in extra_fields:
self.initial[field] = self.instance.condition_details.get(field, None)
else:
self.initial[field] = getattr(self, field, None) or getattr(
self.instance, field, None
)
self.helper = FormHelper()
self.helper.layout = Layout(
Field("submission"),
Field("offered_by"),
Div(FloatingField("condition_type"), css_class="col"),
*[Div(FloatingField(field), css_class="col") for field in extra_fields],
)
def clean(self):
qualification = Qualification.objects.filter(
submission=self.submission, fellow__contributor=self.offered_by
).first()
has_clearance = SubmissionClearance.objects.filter(
profile=self.offered_by.profile,
submission=self.submission,
).exists()
if qualification is None:
self.add_error(
None,
"You must first declare your expertise level for this submission.",
)
elif not qualification.is_qualified:
self.add_error(
None,
"You must be at least marginally qualified to make an assignment offer.",
)
if not has_clearance:
self.add_error(
None,
"You must first declare no competing interests with this submission.",
)
return super().clean()
def save(self):
instance = super().save(commit=False)
if instance.condition_type == "JournalTransfer":
instance.condition_details = {
"alternative_journal_id": self.cleaned_data["alternative_journal_id"].id
}
instance.save()
return instance
# Generated by Django 4.2.15 on 2024-10-09 14:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("submissions", "0165_add_journal_transfer_conditional_offer"),
]
operations = [
migrations.AlterField(
model_name="readiness",
name="status",
field=models.CharField(
choices=[
("perhaps_later", "Perhaps later"),
(
"could_if_transferred",
"I could, if transferred to lower journal",
),
("too_busy", "I would, but I'm currently too busy"),
("conditional", "I would, if transferred"),
("not_interested", "I won't, I'm not interested enough"),
("desk_reject", "I won't, and vote for desk rejection"),
],
max_length=32,
),
),
]
......@@ -18,6 +18,7 @@ class Readiness(models.Model):
STATUS_TOO_BUSY = "too_busy"
STATUS_NOT_INTERESTED = "not_interested"
STATUS_DESK_REJECT = "desk_reject"
STATUS_CONDITIONAL = "conditional"
STATUS_CHOICES = (
(STATUS_PERHAPS_LATER, "Perhaps later"),
(
......@@ -25,6 +26,7 @@ class Readiness(models.Model):
"I could, if transferred to lower journal",
),
(STATUS_TOO_BUSY, "I would, but I'm currently too busy"),
(STATUS_CONDITIONAL, "I would, if transferred"),
(STATUS_NOT_INTERESTED, "I won't, I'm not interested enough"),
(STATUS_DESK_REJECT, "I won't, and vote for desk rejection"),
)
......
{% load crispy_forms_tags %}
{% load ethics_extras %}
<div class="row mb-0">
<form id="conditional-assignment-offer-pool-form-{{ submission.id }}"
class="col"
hx-post="{{ request.path }}"
hx-swap="outerHTML"
hx-target="closest div"
hx-trigger="change delay:250ms">
<div class="row mb-0 align-items-center">
{% crispy form %}
{% if request.method == "POST" %}
<div class="col-auto">
<button hx-vals='{"submit": "Make offer"}'
hx-post="{{ request.path }}"
hx-confirm="Are you sure you want to make this offer? You will not be able to change it later, and you will automatically become EIC of the submission if the offer is accepted."
class="btn btn-primary btn-sm">Make offer</button>
</div>
{% endif %}
</div>
</form>
</div>
......@@ -11,18 +11,26 @@
hx-post="{{ request.path }}"
hx-swap="outerHTML"
hx-target="closest div"
hx-trigger="change delay:1s, CI-clearance-asserted from:closest div">
hx-sync="closest form:replace"
hx-trigger="change delay:500ms, CI-clearance-asserted from:closest div, conditional-assignment-offer-made from:closest div">
{% crispy form %}
</form>
<div class="col-12 col-lg">{% include "ethics/_hx_submission_ethics.html" %}</div>
{% if readiness == 'conditional' %}
<div hx-get="{% url "submissions:pool:_hx_conditional_assignment_offer_form" submission.preprint.identifier_w_vn_nr %}"
hx-trigger="intersect once"></div>
{% endif %}
{% if not clearance and readiness and readiness != "perhaps_later" %}
<div id="submission-{{ submission.id }}-crossref-CI-audit"
class="col-12"
hx-get="{% url 'ethics:_hx_submission_competing_interest_crossref_audit' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}"
hx-trigger="revealed once">
<div class="btn btn-secondary htmx-indicator">Searching CrossRef for common works ...</div>
<div class="btn btn-secondary htmx-indicator">
Searching CrossRef for common works ...
</div>
</div>
{% endif %}
......
......@@ -34,6 +34,11 @@ urlpatterns = [ # building on /submissions/pool/
views_appraisal._hx_radio_appraisal_form,
name="_hx_radio_appraisal_form",
),
path(
"conditional_assignment_offer",
views_appraisal._hx_conditional_assignment_offer_form,
name="_hx_conditional_assignment_offer_form",
),
]
),
),
......
......@@ -3,12 +3,15 @@ __license__ = "AGPL v3"
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404, redirect, render
from django.template.response import TemplateResponse
from django.urls import reverse
from colleges.permissions import fellowship_required
from submissions.forms.appraisal import RadioAppraisalForm
from submissions.forms.appraisal import (
ConditionalAssignmentOfferInlineForm,
RadioAppraisalForm,
)
from submissions.models import Submission, Qualification, Readiness
from submissions.forms import QualificationForm, ReadinessForm
......@@ -61,3 +64,43 @@ def _hx_radio_appraisal_form(request, identifier_w_vn_nr=None):
"submissions/pool/_hx_radio_appraisal_form.html",
context,
)
@fellowship_required()
def _hx_conditional_assignment_offer_form(request, identifier_w_vn_nr=None):
submission = get_object_or_404(
Submission.objects.in_pool(request.user),
preprint__identifier_w_vn_nr=identifier_w_vn_nr,
)
fellow = request.user.contributor.session_fellowship(request)
offer = submission.conditional_assignment_offers.filter(
offered_by=fellow.contributor
).first()
form = ConditionalAssignmentOfferInlineForm(
request.POST or None,
instance=offer,
submission=submission,
offered_by=fellow.contributor,
readonly=request.method == "GET" and offer is not None,
)
if request.method == "POST" and request.POST.get("submit"):
if form.is_valid():
form.save()
response = HttpResponse()
response["HX-Trigger"] = "conditional-assignment-offer-made"
return response
context = {
"submission": submission,
"form": form,
"offer": offer,
}
return TemplateResponse(
request,
"submissions/pool/_hx_conditional_assignment_offer_form.html",
context,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment