diff --git a/production/admin.py b/production/admin.py index 747832fc6a6b177c1ac443b97b92578006fb05ae..2880659cd81d1d9efe90260d12ff1c7c9bbc23b5 100644 --- a/production/admin.py +++ b/production/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from guardian.admin import GuardedModelAdmin -from .models import ProductionStream, ProductionEvent, ProductionUser, Proof +from .models import ProductionStream, ProductionEvent, ProductionUser, Proofs def event_count(obj): @@ -35,7 +35,7 @@ class ProductionProofsAdmin(admin.ModelAdmin): list_filter = ['status', 'accessible_for_authors'] -admin.site.register(Proof, ProductionProofsAdmin) +admin.site.register(Proofs, ProductionProofsAdmin) admin.site.register(ProductionUser) admin.site.register(ProductionEvent) admin.site.register(ProductionStream, ProductionStreamAdmin) diff --git a/production/apps.py b/production/apps.py index e08481e723d02cee2c63470bf7991bb214c79a10..c7df2ea8326bea092da1a6f5ce4562710650dafd 100644 --- a/production/apps.py +++ b/production/apps.py @@ -8,8 +8,8 @@ class ProductionConfig(AppConfig): def ready(self): super().ready() - from .models import ProductionEvent, ProductionStream, Proof - from .signals import notify_new_event, notify_new_stream, notify_proof_upload + from .models import ProductionEvent, ProductionStream, Proofs + from .signals import notify_new_event, notify_new_stream, notify_proofs_upload post_save.connect(notify_new_event, sender=ProductionEvent) post_save.connect(notify_new_stream, sender=ProductionStream) - post_save.connect(notify_proof_upload, sender=Proof) + post_save.connect(notify_proofs_upload, sender=Proofs) diff --git a/production/constants.py b/production/constants.py index 56884a9f89680a1a3e62ca359da8ad50539fe449..e37691c7a445b05bc6081a2c7ca3adacc5e3eee9 100644 --- a/production/constants.py +++ b/production/constants.py @@ -32,19 +32,18 @@ PRODUCTION_EVENTS = ( (EVENT_HOUR_REGISTRATION, 'Hour registration'), ) -PROOF_UPLOADED = 'uploaded' -PROOF_SENT = 'sent' -PROOF_ACCEPTED_SUP = 'accepted_sup' -PROOF_DECLINED_SUP = 'declined_sup' -PROOF_ACCEPTED = 'accepted' -PROOF_DECLINED = 'declined' -PROOF_RENEWED = 'renewed' -PROOF_STATUSES = ( - (PROOF_UPLOADED, 'Proofs uploaded'), - (PROOF_SENT, 'Proofs sent to authors'), - (PROOF_ACCEPTED_SUP, 'Proofs accepted by supervisor'), - (PROOF_DECLINED_SUP, 'Proofs declined by supervisor'), - (PROOF_ACCEPTED, 'Proofs accepted by authors'), - (PROOF_DECLINED, 'Proofs declined by authors'), - (PROOF_RENEWED, 'Proofs renewed'), +PROOFS_UPLOADED = 'uploaded' +PROOFS_SENT = 'sent' +PROOFS_ACCEPTED_SUP = 'accepted_sup' +PROOFS_DECLINED_SUP = 'declined_sup' +PROOFS_DECLINED = 'declined' +PROOFS_RENEWED = 'renewed' +PROOFS_STATUSES = ( + (PROOFS_UPLOADED, 'Proofs uploaded'), + (PROOFS_SENT, 'Proofs sent to authors'), + (PROOFS_ACCEPTED_SUP, 'Proofs accepted by supervisor'), + (PROOFS_DECLINED_SUP, 'Proofs declined by supervisor'), + (PROOFS_ACCEPTED, 'Proofs accepted by authors'), + (PROOFS_DECLINED, 'Proofs declined by authors'), + (PROOFS_RENEWED, 'Proofs renewed'), ) diff --git a/production/forms.py b/production/forms.py index 1ec87f0f5186c2ba912413791187afdbf8d780e3..bec9a69c289a2564b7ddebc66efc1fb7eb894b2d 100644 --- a/production/forms.py +++ b/production/forms.py @@ -5,7 +5,7 @@ from django.utils.dates import MONTHS from django.db.models import Sum from . import constants -from .models import ProductionUser, ProductionStream, ProductionEvent, Proof +from .models import ProductionUser, ProductionStream, ProductionEvent, Proofs from .signals import notify_stream_status_change today = datetime.datetime.today() @@ -176,45 +176,47 @@ class ProductionUserMonthlyActiveFilter(forms.Form): return output -class ProofUploadForm(forms.ModelForm): +class ProofsUploadForm(forms.ModelForm): class Meta: - model = Proof + model = Proofs fields = ('attachment',) -class ProofDecisionForm(forms.ModelForm): +class ProofsDecisionForm(forms.ModelForm): decision = forms.ChoiceField(choices=[(True, 'Accept Proofs for publication'), (False, 'Decline Proofs for publication')]) - comments = forms.CharField(required=False, widget=forms.Textarea) + feedback = forms.CharField(required=False, widget=forms.Textarea) class Meta: - model = Proof + model = Proofs fields = () def save(self, commit=True): - proof = self.instance + proofs = self.instance decision = self.cleaned_data['decision'] - comments = self.cleaned_data['comments'] - if decision: - proof.status = constants.PROOF_ACCEPTED - if proof.stream.status in [constants.PROOFS_PRODUCED, - constants.PROOF_CHECKED, - constants.PROOFS_SENT, - constants.PROOFS_CORRECTED]: + comments = self.cleaned_data['feedback'] + + if decision in ['True', True]: + proofs.status = constants.PROOFS_ACCEPTED + if proofs.stream.status in [constants.PROOFS_PRODUCED, + constants.PROOFS_CHECKED, + constants.PROOFS_SENT, + constants.PROOFS_CORRECTED]: # Force status change on Stream if appropriate - proof.stream.status = constants.PROOFS_ACCEPTED + proofs.stream.status = constants.PROOFS_ACCEPTED else: - proof.status = constants.PROOF_DECLINED + proofs.status = constants.PROOFS_DECLINED + proofs.stream.status = constants.PROOFS_RETURNED if commit: - proof.save() - proof.stream.save() + proofs.save() + proofs.stream.save() prodevent = ProductionEvent( - stream=proof.stream, + stream=proofs.stream, event='status', - comments='Received comments: {comments}'.format(comments=comments), - noted_by=proof.stream.supervisor + comments='Received feedback: {comments}'.format(comments=comments), + noted_by=proofs.stream.supervisor ) prodevent.save() - return proof + return proofs diff --git a/production/managers.py b/production/managers.py index 410c02b9c364315bae8284f0d5664d6c763efcd1..5bd1b5c00b0adc2df29de3e8b7c59881a4235fec 100644 --- a/production/managers.py +++ b/production/managers.py @@ -1,14 +1,14 @@ from django.db import models -from .constants import PRODUCTION_STREAM_COMPLETED +from . import constants class ProductionStreamQuerySet(models.QuerySet): def completed(self): - return self.filter(status=PRODUCTION_STREAM_COMPLETED) + return self.filter(status=constants.PRODUCTION_STREAM_COMPLETED) def ongoing(self): - return self.exclude(status=PRODUCTION_STREAM_COMPLETED) + return self.exclude(status=constants.PRODUCTION_STREAM_COMPLETED) def filter_for_user(self, production_user): """ @@ -27,3 +27,7 @@ class ProductionEventManager(models.Manager): class ProofsQuerySet(models.QuerySet): def for_authors(self): return self.filter(accessible_for_authors=True) + + def can_be_send(self): + return self.filter(status__in=[constants.PROOFS_UPLOADED, constants.PROOFS_SENT, + constants.PROOFS_ACCEPTED_SUP]) diff --git a/production/migrations/0030_auto_20171009_2111.py b/production/migrations/0030_auto_20171009_2111.py new file mode 100644 index 0000000000000000000000000000000000000000..928cc21970a30445f1f84f3a7ab46b9c3e8342df --- /dev/null +++ b/production/migrations/0030_auto_20171009_2111.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-09 19:11 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('production', '0029_productionstream_invitations_officer'), + ] + + operations = [ + migrations.RenameModel( + old_name='Proof', + new_name='Proofs', + ), + ] diff --git a/production/models.py b/production/models.py index 3608fd490927071a4946dd03c33b9eabb8111665..3c55f1a91ebd3c88b0a6c54abf78a4905ad59b41 100644 --- a/production/models.py +++ b/production/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -8,9 +7,9 @@ from django.utils.functional import cached_property from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_INITIATED, PRODUCTION_EVENTS,\ EVENT_MESSAGE, EVENT_HOUR_REGISTRATION, PRODUCTION_STREAM_COMPLETED,\ - PROOF_STATUSES, PROOF_UPLOADED + PROOFS_STATUSES, PROOFS_UPLOADED from .managers import ProductionStreamQuerySet, ProductionEventManager, ProofsQuerySet -from .utils import proof_id_to_slug +from .utils import proofs_id_to_slug from finances.models import WorkLog from scipost.storage import SecureFileStorage @@ -107,29 +106,29 @@ def proofs_upload_location(instance, filename): filename=filename) -class Proof(models.Model): +class Proofs(models.Model): """ - A Proof directly related to a ProductionStream and Submission in SciPost. - It's meant to help the Production team + Proofs are directly related to a ProductionStream and Submission in SciPost. """ attachment = models.FileField(upload_to=proofs_upload_location, storage=SecureFileStorage()) version = models.PositiveSmallIntegerField(default=0) stream = models.ForeignKey('production.ProductionStream', related_name='proofs') uploaded_by = models.ForeignKey('production.ProductionUser', related_name='+') created = models.DateTimeField(auto_now_add=True) - status = models.CharField(max_length=16, choices=PROOF_STATUSES, default=PROOF_UPLOADED) + status = models.CharField(max_length=16, choices=PROOFS_STATUSES, default=PROOFS_UPLOADED) accessible_for_authors = models.BooleanField(default=False) objects = ProofsQuerySet.as_manager() class Meta: ordering = ['stream', 'version'] + verbose_name_plural = 'Proofs' def get_absolute_url(self): - return reverse('production:proof_pdf', kwargs={'slug': self.slug}) + return reverse('production:proofs_pdf', kwargs={'slug': self.slug}) def __str__(self): - return 'Proof {version} for Stream {stream}'.format( + return 'Proofs {version} for Stream {stream}'.format( version=self.version, stream=self.stream.submission.title) def save(self, *args, **kwargs): @@ -140,4 +139,4 @@ class Proof(models.Model): @property def slug(self): - return proof_id_to_slug(self.id) + return proofs_id_to_slug(self.id) diff --git a/production/signals.py b/production/signals.py index 4ee8e46494cd4b5367d48ebecd1d245319b13e5f..c712528527cec7aace9a12f535cbac956a7e2f6e 100644 --- a/production/signals.py +++ b/production/signals.py @@ -85,7 +85,7 @@ def notify_stream_status_change(sender, instance, created, **kwargs): verb=' changed the Production Stream status.', target=instance) -def notify_proof_upload(sender, instance, created, **kwargs): +def notify_proofs_upload(sender, instance, created, **kwargs): if created and instance.stream.supervisor: notify.send(sender=sender, recipient=instance.stream.supervisor.user, actor=instance.uploaded_by.user, verb=' uploaded new Proofs to Production Stream.', diff --git a/production/templates/production/partials/production_events.html b/production/templates/production/partials/production_events.html index 13ced729545c9fcd709157d5b8b409a21f16822c..13b1b0251dd3cbac9fe0a1c524fa349db5ff56ae 100644 --- a/production/templates/production/partials/production_events.html +++ b/production/templates/production/partials/production_events.html @@ -7,7 +7,7 @@ <div> <strong>{{ event.noted_by.user.first_name }} {{ event.noted_by.user.last_name }}</strong> <br> - {{ event.get_event_display }} + {{ event.get_event_displa|linebreaksbr }} </div> <div class="text-muted text-right d-flex justify-content-end"> <div> diff --git a/production/templates/production/partials/production_stream_card_completed.html b/production/templates/production/partials/production_stream_card_completed.html index eaac3f05c54f8bffb573c7dcc7d16c68fddc2e45..99165bd4bdd764038d8433eab2c05aeef933d14c 100644 --- a/production/templates/production/partials/production_stream_card_completed.html +++ b/production/templates/production/partials/production_stream_card_completed.html @@ -46,12 +46,27 @@ {% if "can_work_for_stream" in sub_perms %} <h3>Proofs</h3> <ul> - {% for proof in stream.proofs.all %} + {% for proofs in stream.proofs.all %} <li class="py-1"> - <a href="{% url 'production:proof' stream_id=stream.id version=proof.version %}">Version {{ proof.version }}</a><br> - Uploaded by {{ proof.uploaded_by.user.first_name }} {{ proof.uploaded_by.user.last_name }}<br> - Accessible for authors: <strong>{{ proof.accessible_for_authors|yesno:'Yes,No' }}</strong><br> - <span class="label label-secondary label-sm">{{ proof.get_status_display }}</span> + <a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Version {{ proofs.version }}</a><br> + Uploaded by {{ proofs.uploaded_by.user.first_name }} {{ proofs.uploaded_by.user.last_name }}<br> + Accessible for authors: {{ proofs.accessible_for_authors|yesno:'<strong>Yes</strong>,No'|safe }}<br> + + {% if perms.scipost.can_run_proofs_by_authors %} + {% if proofs.status == 'uploaded' %} + <strong><span class="text-danger">See details for open actions:</span></strong> + <ul> + <li><a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Accept or decline proofs</a></li> + </ul> + {% elif proofs.status == 'accepted_sup' %} + <strong><span class="text-danger">See details for open actions:</span></strong> + <ul> + <li><a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Send proofs to authors</a></li> + </ul> + {% endif %} + {% endif %} + + <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span> </li> {% empty %} <li>No Proofs found.</li> diff --git a/production/templates/production/proofs.html b/production/templates/production/proofs.html index cf469815995a8761a6e98661c93c2ef4dceb2a50..8a2f4071aadfd9296f1939117d332af4933f4738 100644 --- a/production/templates/production/proofs.html +++ b/production/templates/production/proofs.html @@ -3,7 +3,7 @@ {% block breadcrumb_items %} {{block.super}} <a href="{{ stream.get_absolute_url }}" class="breadcrumb-item">Production Stream</a> - <span class="breadcrumb-item">Proofs (version {{ proof.version }})</span> + <span class="breadcrumb-item">Proofs (version {{ proofs.version }})</span> {% endblock %} {% load bootstrap %} @@ -12,7 +12,7 @@ <div class="row"> <div class="col-12"> - <h1 class="highlight">Proofs (version {{ proof.version }})</h1> + <h1 class="highlight">Proofs (version {{ proofs.version }})</h1> {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %} </div> </div> @@ -20,27 +20,27 @@ <div class="col-12"> <h3>Info</h3> <ul> - <li>Version: {{ proof.version }}</li> - <li>Status: <span class="label label-secondary label-sm">{{ proof.get_status_display }}</span></li> - <li>Uploaded by: {{ proof.uploaded_by }}</li> - <li>Accessible for Authors: {{ proof.accessible_for_authors|yesno:'Yes,No' }}</li> + <li>Version: {{ proofs.version }}</li> + <li>Status: <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span></li> + <li>Uploaded by: {{ proofs.uploaded_by }}</li> + <li>Accessible for Authors: {{ proofs.accessible_for_authors|yesno:'Yes,No' }}</li> </ul> <h3>Actions</h3> <ul> - <li><a href="{% url 'production:proof_pdf' proof.slug %}" target="_blank">Download file</a></li> + <li><a href="{% url 'production:proofs_pdf' proofs.slug %}" target="_blank">Download file</a></li> {% if perms.scipost.can_run_proofs_by_authors %} - {% if proof.status == 'uploaded' %} + {% if proofs.status == 'uploaded' %} <li> - <a href="{% url 'production:decision' proof.stream.id proof.version 'accept' %}">Accept proofs</a> + <a href="{% url 'production:decision' proofs.stream.id proofs.version 'accept' %}">Accept proofs</a> · - <a href="{% url 'production:decision' proof.stream.id proof.version 'decline' %}" class="text-danger">Decline proofs</a> + <a href="{% url 'production:decision' proofs.stream.id proofs.version 'decline' %}" class="text-danger">Decline proofs</a> </li> - {% elif proof.status == 'accepted_sup' %} - <li><a href="{% url 'production:send_proofs' proof.stream.id proof.version %}">Send proofs to authors</a></li> + {% elif proofs.status == 'accepted_sup' %} + <li><a href="{% url 'production:send_proofs' proofs.stream.id proofs.version %}">Send proofs to authors</a></li> {% endif %} - {% if proof.status != 'uploaded' %} - <li><a href="{% url 'production:toggle_accessibility' proof.stream.id proof.version %}">{{ proof.accessible_for_authors|yesno:'Hide,Make accessible' }} for authors</a></li> + {% if proofs.status != 'uploaded' %} + <li><a href="{% url 'production:toggle_accessibility' proofs.stream.id proofs.version %}">{{ proofs.accessible_for_authors|yesno:'Hide,Make accessible' }} for authors</a></li> {% endif %} {% endif %} </ul> diff --git a/production/urls.py b/production/urls.py index 66349aec7700bbc76cb4971cf0fc6573091cb8df..a4c6703083ab5420c1461e5ee3d2c334539e2b3a 100644 --- a/production/urls.py +++ b/production/urls.py @@ -14,7 +14,7 @@ urlpatterns = [ url(r'^streams/(?P<stream_id>[0-9]+)/proofs/upload$', production_views.upload_proofs, name='upload_proofs'), url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)$', - production_views.proof, name='proof'), + production_views.proofs, name='proofs'), url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)/decision/(?P<decision>accept|decline)$', production_views.decision, name='decision'), url(r'^streams/(?P<stream_id>[0-9]+)/proofs/(?P<version>[0-9]+)/send_to_authors$', @@ -42,7 +42,7 @@ urlpatterns = [ url(r'^events/(?P<event_id>[0-9]+)/delete', production_views.DeleteEventView.as_view(), name='delete_event'), url(r'^proofs/(?P<slug>[0-9]+)$', - production_views.proof_pdf, name='proof_pdf'), + production_views.proofs_pdf, name='proofs_pdf'), url(r'^proofs/(?P<slug>[0-9]+)/decision$', production_views.author_decision, name='author_decision'), ] diff --git a/production/utils.py b/production/utils.py index f173d7a42a5bd7c0e88145715bfc29a9d3d3fb41..12d31bc778752d05ce0ed9d39cb549bb34c5dfc2 100644 --- a/production/utils.py +++ b/production/utils.py @@ -1,6 +1,6 @@ -def proof_id_to_slug(id): +def proofs_id_to_slug(id): return int(id) + 8932 -def proof_slug_to_id(slug): +def proofs_slug_to_id(slug): return int(slug) - 8932 diff --git a/production/views.py b/production/views.py index b4f5d56a3bd401f1c1bb27c3efb0bae557e24c90..83f047c4dc23926191f4d33d9ac78ed7ac3a75d3 100644 --- a/production/views.py +++ b/production/views.py @@ -18,13 +18,13 @@ from guardian.shortcuts import assign_perm, remove_perm from finances.forms import WorkLogForm from . import constants -from .models import ProductionUser, ProductionStream, ProductionEvent, Proof +from .models import ProductionUser, ProductionStream, ProductionEvent, Proofs from .forms import ProductionEventForm, AssignOfficerForm, UserToOfficerForm,\ - AssignSupervisorForm, StreamStatusForm, ProofUploadForm, ProofDecisionForm,\ + AssignSupervisorForm, StreamStatusForm, ProofsUploadForm, ProofsDecisionForm,\ AssignInvitationsOfficerForm from .permissions import is_production_user from .signals import notify_stream_status_change, notify_new_stream_assignment -from .utils import proof_slug_to_id +from .utils import proofs_slug_to_id ###################### @@ -62,7 +62,7 @@ def production(request, stream_id=None): context['assign_supervisor_form'] = AssignSupervisorForm() context['prodevent_form'] = ProductionEventForm() context['work_log_form'] = WorkLogForm() - context['upload_proofs_form'] = ProofUploadForm() + context['upload_proofs_form'] = ProofsUploadForm() except ProductionStream.DoesNotExist: pass @@ -105,7 +105,7 @@ def stream(request, stream_id): assign_officer_form = AssignOfficerForm() assign_invitiations_officer_form = AssignInvitationsOfficerForm() assign_supervisor_form = AssignSupervisorForm() - upload_proofs_form = ProofUploadForm() + upload_proofs_form = ProofsUploadForm() work_log_form = WorkLogForm() status_form = StreamStatusForm(instance=stream, production_user=request.user.production_user) @@ -397,14 +397,14 @@ def upload_proofs(request, stream_id): if not checker.has_perm('can_work_for_stream', stream): return redirect(reverse('production:production')) - form = ProofUploadForm(request.POST or None, request.FILES or None) + form = ProofsUploadForm(request.POST or None, request.FILES or None) if form.is_valid(): - proof = form.save(commit=False) - proof.stream = stream - proof.uploaded_by = request.user.production_user - proof.save() - Proof.objects.filter(stream=stream).exclude(version=proof.version).update( - status=constants.PROOF_RENEWED) + proofs = form.save(commit=False) + proofs.stream = stream + proofs.uploaded_by = request.user.production_user + proofs.save() + Proofs.objects.filter(stream=stream).exclude(version=proofs.version).exclude( + status=constants.PROOFS_ACCEPTED).update(status=constants.PROOFS_RENEWED) messages.success(request, 'Proof uploaded.') # Update Stream status @@ -418,7 +418,7 @@ def upload_proofs(request, stream_id): prodevent = ProductionEvent( stream=stream, event='status', - comments='New Proofs uploaded, version {v}'.format(v=proof.version), + comments='New Proofs uploaded, version {v}'.format(v=proofs.version), noted_by=request.user.production_user ) prodevent.save() @@ -433,7 +433,7 @@ def upload_proofs(request, stream_id): @is_production_user() @permission_required('scipost.can_view_production', raise_exception=True) -def proof(request, stream_id, version): +def proofs(request, stream_id, version): """ Called by a member of the Production Team. Upload the production version .pdf of a submission. @@ -444,39 +444,39 @@ def proof(request, stream_id, version): return redirect(reverse('production:production')) try: - proof = stream.proofs.get(version=version) - except Proof.DoesNotExist: + proofs = stream.proofs.get(version=version) + except Proofs.DoesNotExist: raise Http404 context = { 'stream': stream, - 'proof': proof + 'proofs': proofs } return render(request, 'production/proofs.html', context) -def proof_pdf(request, slug): - """ Open Proof pdf. """ +def proofs_pdf(request, slug): + """ Open Proofs pdf. """ if not request.user.is_authenticated: # Don't use the decorator but this strategy, # because now it will return 404 instead of a redirect to the login page. raise Http404 - proof = Proof.objects.get(id=proof_slug_to_id(slug)) - stream = proof.stream + proofs = Proofs.objects.get(id=proofs_slug_to_id(slug)) + stream = proofs.stream # Check if user has access! checker = ObjectPermissionChecker(request.user) access = checker.has_perm('can_work_for_stream', stream) and request.user.has_perm('scipost.can_view_production') if not access and request.user.contributor: - access = request.user.contributor in proof.stream.submission.authors.all() + access = request.user.contributor in proofs.stream.submission.authors.all() if not access: raise Http404 # Passed the test! The user may see the file! - content_type, encoding = mimetypes.guess_type(proof.attachment.path) + content_type, encoding = mimetypes.guess_type(proofs.attachment.path) content_type = content_type or 'application/octet-stream' - response = HttpResponse(proof.attachment.read(), content_type=content_type) + response = HttpResponse(proofs.attachment.read(), content_type=content_type) response["Content-Encoding"] = encoding return response @@ -489,16 +489,17 @@ def author_decision(request, slug): Accept or Decline? This will be asked if proof status is `ACCEPTED_SUP` and will be handled in this view. """ - proof = Proof.objects.get(id=proof_slug_to_id(slug)) - stream = proof.stream + proofs = Proofs.objects.get(id=proofs_slug_to_id(slug)) + stream = proofs.stream # Check if user has access! - if request.user.contributor not in proof.stream.submission.authors.all(): + if request.user.contributor not in proofs.stream.submission.authors.all(): raise Http404 - form = ProofDecisionForm(request.POST or None, instance=proof) + form = ProofsDecisionForm(request.POST or None, instance=proofs) if form.is_valid(): - proof = form.save() + proofs = form.save() + notify_stream_status_change(request.user, stream, False) messages.success(request, 'Your decision has been sent.') return redirect(stream.submission.get_absolute_url()) @@ -516,12 +517,12 @@ def toggle_accessibility(request, stream_id, version): return redirect(reverse('production:production')) try: - proof = stream.proofs.exclude(status=constants.PROOF_UPLOADED).get(version=version) - except Proof.DoesNotExist: + proofs = stream.proofs.exclude(status=constants.PROOFS_UPLOADED).get(version=version) + except Proofs.DoesNotExist: raise Http404 - proof.accessible_for_authors = not proof.accessible_for_authors - proof.save() + proofs.accessible_for_authors = not proofs.accessible_for_authors + proofs.save() messages.success(request, 'Proofs accessibility updated.') return redirect(stream.get_absolute_url()) @@ -531,7 +532,7 @@ def toggle_accessibility(request, stream_id, version): @transaction.atomic def decision(request, stream_id, version, decision): """ - Send/open proofs to the authors. + Send/open proofs to the authors. This decision is taken by the supervisor. """ stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) checker = ObjectPermissionChecker(request.user) @@ -539,25 +540,26 @@ def decision(request, stream_id, version, decision): return redirect(reverse('production:production')) try: - proof = stream.proofs.get(version=version, status=constants.PROOF_UPLOADED) - except Proof.DoesNotExist: + proofs = stream.proofs.get(version=version, status=constants.PROOFS_UPLOADED) + except Proofs.DoesNotExist: raise Http404 if decision == 'accept': - proof.status = constants.PROOF_ACCEPTED_SUP + proofs.status = constants.PROOFS_ACCEPTED_SUP stream.status = constants.PROOFS_CHECKED decision = 'accepted' else: - proof.status = constants.PROOF_DECLINED_SUP + proofs.status = constants.PROOFS_DECLINED_SUP + proofs.accessible_for_authors = False stream.status = constants.PROOFS_TASKED decision = 'declined' stream.save() - proof.save() + proofs.save() prodevent = ProductionEvent( stream=stream, event='status', - comments='Proofs version {version} are {decision}.'.format(version=proof.version, + comments='Proofs version {version} are {decision}.'.format(version=proofs.version, decision=decision), noted_by=request.user.production_user ) @@ -579,15 +581,16 @@ def send_proofs(request, stream_id, version): return redirect(reverse('production:production')) try: - proof = stream.proofs.get(version=version, status=constants.PROOF_UPLOADED) - except Proof.DoesNotExist: + proof = stream.proofs.can_be_send().get(version=version) + except Proofs.DoesNotExist: raise Http404 - proof.status = constants.PROOF_SENT + proof.status = constants.PROOFS_SENT proof.accessible_for_authors = True proof.save() - if stream.status not in [constants.PROOFS_PUBLISHED, constants.PROOFS_CITED]: + if stream.status not in [constants.PROOFS_PUBLISHED, constants.PROOFS_CITED, + constants.PRODUCTION_STREAM_COMPLETED]: stream.status = constants.PROOFS_SENT stream.save() diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index 9e8c837bc96a0ebf9693afbbe1a8d4594fff2665..1a0b85913247e8d3cc940466be9541f3b1893a4a 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -117,20 +117,20 @@ </div> {% endif %} -{% if is_author %} +{% if is_author or user|is_in_group:'Editorial Administrators' %} {% if submission.production_stream.proofs.for_authors.exists %} <div class="mb-4" id="proofsslist"> <h2>Proofs</h2> <ul> - {% for proof in submission.production_stream.proofs.for_authors %} + {% for proofs in submission.production_stream.proofs.for_authors %} <li> - <a href="{{ proof.get_absolute_url }}" target="_blank">Download version {{ proof.version }}</a> · uploaded: {{ proof.created|date:"DATE_FORMAT" }} · - status: <span class="label label-secondary label-sm">{{ proof.get_status_display }}</span> - {% if proof.status == 'accepted_sup' and proof_decision_form %} + <a href="{{ proofs.get_absolute_url }}" target="_blank">Download version {{ proofs.version }}</a> · uploaded: {{ proofs.created|date:"DATE_FORMAT" }} · + status: <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span> + {% if proofs.status == 'accepted_sup' and proofs_decision_form and is_author %} <h3 class="mb-0 mt-2">Please advise the Production Team on your findings on Proofs version {{ proof.version }}</h3> - <form method="post" action="{% url 'production:author_decision' proof.slug %}" class="my-2"> + <form method="post" action="{% url 'production:author_decision' proofs.slug %}" class="my-2"> {% csrf_token %} - {{ proof_decision_form|bootstrap }} + {{ proofs_decision_form|bootstrap }} <input class="btn btn-primary btn-sm" type="submit" value="Submit"> </form> {% endif %} diff --git a/submissions/views.py b/submissions/views.py index 4ed908994533cbb79abac583505dd98ebc204d4c..b0fbd035edfcb72d960c8a2889e03ea2e9d41e1f 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -41,7 +41,7 @@ from scipost.utils import Utils from scipost.permissions import is_tester from comments.forms import CommentForm -from production.forms import ProofDecisionForm +from production.forms import ProofsDecisionForm from production.models import ProductionStream import strings @@ -190,7 +190,7 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): except Report.DoesNotExist: unfinished_report_for_user = None - context['proof_decision_form'] = ProofDecisionForm() + context['proofs_decision_form'] = ProofsDecisionForm() except AttributeError: is_author = False is_author_unchecked = False