diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 669f3bc7ed10183e7bcea091ff22551cd217818e..54e67dda0b80013f74b3d35bfc4d2e48b1ae2bb5 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -93,6 +93,7 @@ INSTALLED_APPS = ( 'submissions', 'theses', 'virtualmeetings', + 'production', 'webpack_loader', ) diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index f930e4a9312d8e0a5326bac188cb63824c27a412..36ab1c7ced277f745ad08e22922ebd4d594c6c21 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -42,6 +42,7 @@ urlpatterns = [ url(r'^thesis/', include('theses.urls', namespace="theses")), url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")), url(r'^news/', include('news.urls', namespace="news")), + url(r'^production/', include('production.urls', namespace="production")), ] if settings.DEBUG: diff --git a/journals/admin.py b/journals/admin.py index 79667f7bfc31d7d998b1348e5ddcfebd0645009d..48f9f4ee5a90149ca010e5fe22edf9d85e75f7c6 100644 --- a/journals/admin.py +++ b/journals/admin.py @@ -1,14 +1,8 @@ from django.contrib import admin, messages -from journals.models import ProductionStream, ProductionEvent from journals.models import Journal, Volume, Issue, Publication, Deposit -admin.site.register(ProductionStream) - - -admin.site.register(ProductionEvent) - class JournalAdmin(admin.ModelAdmin): search_fields = ['name'] diff --git a/journals/constants.py b/journals/constants.py index 3a3aa9b88e6191b3c6620a33a1d3d84fc5db0d86..6bd5de5cf9a4285d253a22a9bbb638efde5396f0 100644 --- a/journals/constants.py +++ b/journals/constants.py @@ -56,18 +56,3 @@ ISSUE_STATUSES = ( (STATUS_DRAFT, 'Draft'), (STATUS_PUBLISHED, 'Published'), ) - -PRODUCTION_STREAM_STATUS = ( - ('ongoing', 'Ongoing'), - ('completed', 'Completed'), -) - -PRODUCTION_EVENTS = ( - ('assigned_to_supervisor', 'Assigned to Supervisor'), - ('officer_tasked_with_proof_production', 'Officer tasked with proofs production'), - ('proofs_produced', 'Proofs have been produced'), - ('proofs_sent_to_authors', 'Proofs sent to Authors'), - ('proofs_returned_by_authors', 'Proofs returned by Authors'), - ('corrections_implemented', 'Corrections implemented'), - ('authors_have_accepted_proofs', 'Authors have accepted proofs'), -) diff --git a/journals/forms.py b/journals/forms.py index 910bbd5b4f375f2aa40dc63893b542de1dc68a8b..d173a344773c53a27ea61e613631c63cdf7b8edc 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -1,22 +1,11 @@ from django import forms from django.utils import timezone -from .models import ProductionEvent from .models import UnregisteredAuthor, Issue, Publication from submissions.models import Submission -class ProductionEventForm(forms.ModelForm): - class Meta: - model = ProductionEvent - exclude = ['stream', 'noted_on', 'noted_by'] - - def __init__(self, *args, **kwargs): - super(ProductionEventForm, self).__init__(*args, **kwargs) - self.fields['duration'].widget.attrs.update( - {'placeholder': 'HH:MM:SS'}) - class InitiatePublicationForm(forms.Form): accepted_submission = forms.ModelChoiceField( diff --git a/journals/migrations/0023_auto_20170517_1846.py b/journals/migrations/0023_auto_20170517_1846.py new file mode 100644 index 0000000000000000000000000000000000000000..b3462dcdd0e7b5008c780b0446f1324405f07df4 --- /dev/null +++ b/journals/migrations/0023_auto_20170517_1846.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-05-17 16:46 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0022_auto_20170517_1608'), + ] + + operations = [ + migrations.RemoveField( + model_name='productionevent', + name='noted_by', + ), + migrations.RemoveField( + model_name='productionevent', + name='stream', + ), + migrations.RemoveField( + model_name='productionstream', + name='submission', + ), + migrations.DeleteModel( + name='ProductionEvent', + ), + migrations.DeleteModel( + name='ProductionStream', + ), + ] diff --git a/journals/models.py b/journals/models.py index df59b73d6d98560dd1da03fa5dd56ca033794174..920371b7819b43ae6d49c9e35619b65cd36c1ab0 100644 --- a/journals/models.py +++ b/journals/models.py @@ -7,8 +7,7 @@ from django.urls import reverse from .behaviors import doi_journal_validator, doi_volume_validator,\ doi_issue_validator, doi_publication_validator from .constants import SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS,\ - STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES,\ - PRODUCTION_STREAM_STATUS, PRODUCTION_EVENTS + STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES from .helpers import paper_nr_string, journal_name_abbrev_citation from .managers import IssueManager, PublicationManager @@ -17,34 +16,6 @@ from scipost.fields import ChoiceArrayField from scipost.models import Contributor -############## -# Production # -############## - -class ProductionStream(models.Model): - submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE) - opened = models.DateTimeField() - - def __str__(self): - return str(self.submission) - - def total_duration(self): - totdur = self.productionevent_set.all().aggregate(models.Sum('duration')) - return totdur['duration__sum'] - - -class ProductionEvent(models.Model): - stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE) - event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS) - comments = models.TextField(blank=True, null=True) - noted_on = models.DateTimeField(default=timezone.now) - noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE) - duration = models.DurationField(blank=True, null=True) - - def __str__(self): - return '%s: %s' % (str(self.stream.submission), self.get_event_display()) - - ################ # Journals etc # ################ diff --git a/journals/urls/general.py b/journals/urls/general.py index b1a53b0794c6f8892280a6cbe4d4f092a1efc998..76d8ab0b758416a861fe8b654cdd747bb3c4b7be 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -12,11 +12,6 @@ urlpatterns = [ TemplateView.as_view(template_name='journals/journals_terms_and_conditions.html'), name='journals_terms_and_conditions'), - # Production - url(r'^production$', journals_views.production, name='production'), - url(r'^add_production_event/(?P<stream_id>[0-9]+)$', - journals_views.add_production_event, name='add_production_event'), - # Editorial and Administrative Workflow url(r'^initiate_publication$', journals_views.initiate_publication, diff --git a/journals/views.py b/journals/views.py index 08926503bf421fc1ad837e2849527f06bfed8fff..899edf79899b8dd5e12edf3f45d78449de89af82 100644 --- a/journals/views.py +++ b/journals/views.py @@ -15,9 +15,7 @@ from django.http import HttpResponse from .exceptions import PaperNumberingError from .helpers import paper_nr_string -from .models import ProductionStream, ProductionEvent from .models import Journal, Issue, Publication, UnregisteredAuthor -from .forms import ProductionEventForm from .forms import FundingInfoForm, InitiatePublicationForm, ValidatePublicationForm,\ UnregisteredAuthorForm, CreateMetadataXMLForm, CitationListBibitemsForm from .utils import JournalUtils @@ -136,61 +134,6 @@ def issue_detail(request, doi_label): return render(request, 'journals/journal_issue_detail.html', context) -###################### -# Production process # -###################### - -@permission_required('scipost.can_view_production', return_403=True) -def production(request): - """ - Overview page for the production process. - All papers with accepted but not yet published status are included here. - """ - accepted_submissions = Submission.objects.filter( - status='accepted').order_by('latest_activity') - streams = ProductionStream.objects.all().order_by('opened') - prodevent_form = ProductionEventForm() - context = { - 'accepted_submissions': accepted_submissions, - 'streams': streams, - 'prodevent_form': prodevent_form, - } - return render(request, 'journals/production.html', context) - -@permission_required('scipost.can_view_production', return_403=True) -@transaction.atomic -def add_production_event(request, stream_id): - stream = get_object_or_404(ProductionStream, pk=stream_id) - if request.method == 'POST': - prodevent_form = ProductionEventForm(request.POST) - if prodevent_form.is_valid(): - prodevent = ProductionEvent( - stream=stream, - event=prodevent_form.cleaned_data['event'], - comments=prodevent_form.cleaned_data['comments'], - noted_on=timezone.now(), - noted_by=request.user.contributor, - duration=prodevent_form.cleaned_data['duration'],) - prodevent.save() - return redirect(reverse('journals:production')) - else: - errormessage = 'The form was invalidly filled.' - return render(request, 'scipost/error.html', {'errormessage': errormessage}) - else: - errormessage = 'This view can only be posted to.' - return render(request, 'scipost/error.html', {'errormessage': errormessage}) - - - - -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') - ####################### # Publication process # diff --git a/production/__init__.py b/production/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/production/admin.py b/production/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..b20b4a415911ba3ce9e480be1f54844dd858f54b --- /dev/null +++ b/production/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from .models import ProductionStream, ProductionEvent + + +admin.site.register(ProductionStream) + + +admin.site.register(ProductionEvent) diff --git a/production/apps.py b/production/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..d8fba60df00793d7fbaff3cf230a0ba937c9576a --- /dev/null +++ b/production/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProductionConfig(AppConfig): + name = 'production' diff --git a/production/constants.py b/production/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..8a1ba8ea803044b2320086ced3e2f1e726a369da --- /dev/null +++ b/production/constants.py @@ -0,0 +1,21 @@ + + +PRODUCTION_STREAM_STATUS = ( + ('ongoing', 'Ongoing'), + ('completed', 'Completed'), +) + + +PRODUCTION_EVENTS = ( + ('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'), + ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'), + ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'), + ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'), + ('message_supervisor_to_officer', 'Message from Supervisor to Officer'), + ('message_officer_to_supervisor', 'Message from Officer to Supervisor'), + ('proofs_produced', 'Proofs have been produced'), + ('proofs_sent_to_authors', 'Proofs sent to Authors'), + ('proofs_returned_by_authors', 'Proofs returned by Authors'), + ('corrections_implemented', 'Corrections implemented'), + ('authors_have_accepted_proofs', 'Authors have accepted proofs'), +) diff --git a/production/forms.py b/production/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..d783973ecf3be8bbda9a5ca542691260aa755305 --- /dev/null +++ b/production/forms.py @@ -0,0 +1,13 @@ +from django import forms + +from .models import ProductionEvent + + +class ProductionEventForm(forms.ModelForm): + class Meta: + model = ProductionEvent + exclude = ['stream', 'noted_on', 'noted_by'] + widgets = { + 'comments': forms.Textarea(attrs={'rows': 4,}), + 'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'}) + } diff --git a/production/migrations/0001_initial.py b/production/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..3e3ed1ac3f9aff3d6ab3a6ab1f0367aea832859d --- /dev/null +++ b/production/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-05-17 17:23 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('submissions', '0043_auto_20170512_0836'), + ('scipost', '0054_delete_newsitem'), + ] + + operations = [ + migrations.CreateModel( + name='ProductionEvent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('event', models.CharField(choices=[('assigned_to_supervisor', 'Assigned to Supervisor'), ('officer_tasked_with_proof_production', 'Officer tasked with proofs production'), ('proofs_produced', 'Proofs have been produced'), ('proofs_sent_to_authors', 'Proofs sent to Authors'), ('proofs_returned_by_authors', 'Proofs returned by Authors'), ('corrections_implemented', 'Corrections implemented'), ('authors_have_accepted_proofs', 'Authors have accepted proofs')], max_length=64)), + ('comments', models.TextField(blank=True, null=True)), + ('noted_on', models.DateTimeField(default=django.utils.timezone.now)), + ('duration', models.DurationField(blank=True, null=True)), + ('noted_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')), + ], + ), + migrations.CreateModel( + name='ProductionStream', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('opened', models.DateTimeField()), + ('submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='submissions.Submission')), + ], + ), + migrations.AddField( + model_name='productionevent', + name='stream', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='production.ProductionStream'), + ), + ] diff --git a/production/migrations/0002_auto_20170517_1942.py b/production/migrations/0002_auto_20170517_1942.py new file mode 100644 index 0000000000000000000000000000000000000000..53aa4d79751c03ad4491b5da6731abc11d705509 --- /dev/null +++ b/production/migrations/0002_auto_20170517_1942.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-05-17 17:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('production', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='productionevent', + name='event', + field=models.CharField(choices=[('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'), ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'), ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'), ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'), ('message_supervisor_to_officer', 'Message from Supervisor to Officer'), ('message_officer_to_supervisor', 'Message from Officer to Supervisor'), ('proofs_produced', 'Proofs have been produced'), ('proofs_sent_to_authors', 'Proofs sent to Authors'), ('proofs_returned_by_authors', 'Proofs returned by Authors'), ('corrections_implemented', 'Corrections implemented'), ('authors_have_accepted_proofs', 'Authors have accepted proofs')], max_length=64), + ), + ] diff --git a/production/migrations/__init__.py b/production/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/production/models.py b/production/models.py new file mode 100644 index 0000000000000000000000000000000000000000..8badf3eeac4fa5df70ac52dfc24feebff4225391 --- /dev/null +++ b/production/models.py @@ -0,0 +1,34 @@ +from django.db import models +from django.utils import timezone + +from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_EVENTS + +from scipost.models import Contributor + + +############## +# Production # +############## + +class ProductionStream(models.Model): + submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE) + opened = models.DateTimeField() + + def __str__(self): + return str(self.submission) + + def total_duration(self): + totdur = self.productionevent_set.all().aggregate(models.Sum('duration')) + return totdur['duration__sum'] + + +class ProductionEvent(models.Model): + stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE) + event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS) + comments = models.TextField(blank=True, null=True) + noted_on = models.DateTimeField(default=timezone.now) + noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE) + duration = models.DurationField(blank=True, null=True) + + def __str__(self): + return '%s: %s' % (str(self.stream.submission), self.get_event_display()) diff --git a/journals/templates/journals/_production_event_li.html b/production/templates/production/_production_event_li.html similarity index 100% rename from journals/templates/journals/_production_event_li.html rename to production/templates/production/_production_event_li.html diff --git a/journals/templates/journals/_production_stream_card.html b/production/templates/production/_production_stream_card.html similarity index 81% rename from journals/templates/journals/_production_stream_card.html rename to production/templates/production/_production_stream_card.html index f2a78b13fd8770a13b8064ac0bbbbe7557add4b8..fda086794d4c0faa5efcdf4bbc80d314b7dc2a26 100644 --- a/journals/templates/journals/_production_stream_card.html +++ b/production/templates/production/_production_stream_card.html @@ -8,7 +8,7 @@ <h3>Events</h3> <ul> {% for event in stream.productionevent_set.all %} - {% include 'journals/_production_event_li.html' with event=event %} + {% include 'production/_production_event_li.html' with event=event %} {% empty %} <li>No events were found.</li> {% endfor %} @@ -20,7 +20,7 @@ </div> <div class="col-5"> <h3>Add an event to this production stream:</h3> - <form action="{% url 'journals:add_production_event' stream_id=stream.id %}" method="post"> + <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post"> {% csrf_token %} {{ form|bootstrap }} <input type="submit" name="submit" value="Submit"> diff --git a/journals/templates/journals/production.html b/production/templates/production/production.html similarity index 79% rename from journals/templates/journals/production.html rename to production/templates/production/production.html index 2f1b67f3a957bf520a09d7a461936822d45a3161..36b7dcf39eccbc0c9982f5ebe5703962d4955682 100644 --- a/journals/templates/journals/production.html +++ b/production/templates/production/production.html @@ -10,7 +10,7 @@ <ul class="list-group list-group-flush"> {% for stream in streams %} <li class="list-group-item"> - {% include 'journals/_production_stream_card.html' with stream=stream form=prodevent_form %} + {% include 'production/_production_stream_card.html' with stream=stream form=prodevent_form %} </li> <hr/> {% endfor %} diff --git a/production/tests.py b/production/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/production/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/production/urls.py b/production/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..11007074f3a47e420c14b81f62009486dab8f23c --- /dev/null +++ b/production/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls import url + +from production import views as production_views + +urlpatterns = [ + url(r'^$', production_views.production, name='production'), + url(r'^add_event/(?P<stream_id>[0-9]+)$', + production_views.add_event, name='add_event'), +] diff --git a/production/views.py b/production/views.py new file mode 100644 index 0000000000000000000000000000000000000000..8103f5fdbad18954c5a9712147301b502dc2986e --- /dev/null +++ b/production/views.py @@ -0,0 +1,68 @@ +from django.core.urlresolvers import reverse +from django.db import transaction +from django.shortcuts import get_object_or_404, render, redirect +from django.utils import timezone + +from guardian.decorators import permission_required + +from .models import ProductionStream, ProductionEvent +from .forms import ProductionEventForm + +from submissions.models import Submission +from scipost.models import Contributor + + +###################### +# Production process # +###################### + +@permission_required('scipost.can_view_production', return_403=True) +def production(request): + """ + Overview page for the production process. + All papers with accepted but not yet published status are included here. + """ + accepted_submissions = Submission.objects.filter( + status='accepted').order_by('latest_activity') + streams = ProductionStream.objects.all().order_by('opened') + prodevent_form = ProductionEventForm() + context = { + 'accepted_submissions': accepted_submissions, + 'streams': streams, + 'prodevent_form': prodevent_form, + } + return render(request, 'production/production.html', context) + +@permission_required('scipost.can_view_production', return_403=True) +@transaction.atomic +def add_event(request, stream_id): + stream = get_object_or_404(ProductionStream, pk=stream_id) + if request.method == 'POST': + prodevent_form = ProductionEventForm(request.POST) + if prodevent_form.is_valid(): + prodevent = ProductionEvent( + stream=stream, + event=prodevent_form.cleaned_data['event'], + comments=prodevent_form.cleaned_data['comments'], + noted_on=timezone.now(), + noted_by=request.user.contributor, + duration=prodevent_form.cleaned_data['duration'],) + prodevent.save() + return redirect(reverse('production:production')) + else: + errormessage = 'The form was invalidly filled.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + + + +def upload_proofs(request): + """ + TODO + Called by a member of the Production Team. + Upload the production version .pdf of a submission. + """ + return render(request, 'production/upload_proofs.html') diff --git a/submissions/views.py b/submissions/views.py index b49f185de05e6050d7eba0075423db59f14458c4..a3c046f0dca97d4b7c232a9de15eea2490ac9cdd 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -35,6 +35,7 @@ from scipost.utils import Utils from strings import arxiv_caller_errormessages_submissions from comments.forms import CommentForm +from production.models import ProductionStream from django.views.generic.edit import CreateView, FormView from django.views.generic.list import ListView @@ -1290,6 +1291,10 @@ def fix_College_decision(request, rec_id): if recommendation.recommendation in [1, 2, 3]: # Publish as Tier I, II or III recommendation.submission.status = 'accepted' + # Create a ProductionStream object + prodstream = ProductionStream(submission=recommendation.submission, + opened=timezone.now()) + prodstream.save() elif recommendation.recommendation == -3: # Reject recommendation.submission.status = 'rejected'