diff --git a/production/admin.py b/production/admin.py index 64b634398caf4f31004b7488a98a5615c673a8a9..16774de5fb420d01ec1770127df34b489cdae89d 100644 --- a/production/admin.py +++ b/production/admin.py @@ -15,11 +15,13 @@ class ProductionStreamAdminForm(forms.ModelForm): model = ProductionStream fields = '__all__' + class ProductionStreamAdmin(admin.ModelAdmin): search_fields = ['submission'] list_display = ['submission', 'opened', 'status'] form = ProductionStreamAdminForm + admin.site.register(ProductionStream, ProductionStreamAdmin) @@ -31,9 +33,11 @@ class ProductionEventAdminForm(forms.ModelForm): model = ProductionEvent fields = '__all__' + class ProductionEventAdmin(admin.ModelAdmin): search_field = ['stream', 'event', 'comment', 'noted_by'] list_display = ['stream', 'event', 'noted_on', 'noted_by'] form = ProductionEventAdminForm + admin.site.register(ProductionEvent, ProductionEventAdmin) diff --git a/production/constants.py b/production/constants.py index ce9caee48c34a9a78db9235df621d04550b13e91..26590270812cf4365e7a17919a5a7bada0d06eb6 100644 --- a/production/constants.py +++ b/production/constants.py @@ -1,9 +1,10 @@ +PRODUCTION_STREAM_ONGOING = 'ongoing' +PRODUCTION_STREAM_COMPLETED = 'completed' PRODUCTION_STREAM_STATUS = ( - ('ongoing', 'Ongoing'), - ('completed', 'Completed'), + (PRODUCTION_STREAM_ONGOING, 'Ongoing'), + (PRODUCTION_STREAM_COMPLETED, 'Completed'), ) - PRODUCTION_EVENTS = ( ('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'), ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'), diff --git a/production/forms.py b/production/forms.py index 9f47ebe6755bf04607cf9f15f4515f73d7d090f0..fec37e4b4ba232b9432bfc52c173229c640c709a 100644 --- a/production/forms.py +++ b/production/forms.py @@ -6,7 +6,11 @@ from .models import ProductionEvent class ProductionEventForm(forms.ModelForm): class Meta: model = ProductionEvent - exclude = ['stream', 'noted_on', 'noted_by'] + fields = ( + 'event', + 'comments', + 'duration' + ) widgets = { 'comments': forms.Textarea(attrs={'rows': 4}), 'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'}) diff --git a/production/managers.py b/production/managers.py new file mode 100644 index 0000000000000000000000000000000000000000..4379df507a866763f71b20687f4954bfe3eb16bb --- /dev/null +++ b/production/managers.py @@ -0,0 +1,11 @@ +from django.db import models + +from .constants import PRODUCTION_STREAM_COMPLETED, PRODUCTION_STREAM_ONGOING + + +class ProductionStreamManager(models.Manager): + def completed(self): + return self.filter(status=PRODUCTION_STREAM_COMPLETED) + + def ongoing(self): + return self.filter(status=PRODUCTION_STREAM_ONGOING) diff --git a/production/models.py b/production/models.py index 8cd82781cdff1ae47ddb738ef3ecebe2aa3d6eef..f2ec3b93c777423f1a78afc43a799dd23a8458ee 100644 --- a/production/models.py +++ b/production/models.py @@ -1,7 +1,8 @@ from django.db import models from django.utils import timezone -from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_EVENTS +from .constants import PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_ONGOING, PRODUCTION_EVENTS +from .managers import ProductionStreamManager from scipost.models import Contributor @@ -12,16 +13,18 @@ from scipost.models import Contributor class ProductionStream(models.Model): submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE) - opened = models.DateTimeField() + opened = models.DateTimeField(auto_now_add=True) closed = models.DateTimeField(default=timezone.now) status = models.CharField(max_length=32, - choices=PRODUCTION_STREAM_STATUS, default='ongoing') + choices=PRODUCTION_STREAM_STATUS, default=PRODUCTION_STREAM_ONGOING) + + objects = ProductionStreamManager() def __str__(self): return str(self.submission) def total_duration(self): - totdur = self.productionevent_set.all().aggregate(models.Sum('duration')) + totdur = self.productionevent_set.aggregate(models.Sum('duration')) return totdur['duration__sum'] @@ -29,7 +32,7 @@ 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_on = models.DateTimeField(auto_now_add=True) noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE) duration = models.DurationField(blank=True, null=True) diff --git a/production/templates/production/_production_event_li.html b/production/templates/production/_production_event_li.html deleted file mode 100644 index 117a802349c31a275912e4fb38def2448042e36c..0000000000000000000000000000000000000000 --- a/production/templates/production/_production_event_li.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load scipost_extras %} - -<li id="{{ event.id }}"> - <div class="font-weight-bold">{{ event.get_event_display }} <small class="text-muted">noted {{ event.noted_on }} by {{ event.noted_by }}</small> - </div> - {% if event.duration %} - <div><small>Duration: {{ event.duration|duration }}</small></div> - {% endif %} - {% if event.comments %} - <div>{{ event.comments|linebreaks }}</div> - {% endif %} -</li> diff --git a/production/templates/production/_production_events.html b/production/templates/production/_production_events.html new file mode 100644 index 0000000000000000000000000000000000000000..5903e476850d4d3546993abe082171bd99fdcfd2 --- /dev/null +++ b/production/templates/production/_production_events.html @@ -0,0 +1,18 @@ +{% load scipost_extras %} + +<ul> + {% for event in events %} + <li id="{{ event.id }}"> + <div class="font-weight-bold">{{ event.get_event_display }} <small class="text-muted">noted {{ event.noted_on }} by {{ event.noted_by }}</small> + </div> + {% if event.duration %} + <div><small>Duration: {{ event.duration|duration }}</small></div> + {% endif %} + {% if event.comments %} + <div>{{ event.comments|linebreaks }}</div> + {% endif %} + </li> + {% empty %} + <li>No events were found.</li> + {% endfor %} +</ul> diff --git a/production/templates/production/_production_stream_card.html b/production/templates/production/_production_stream_card.html index c39436c5c30b365de02d699b205fcafdc0d38806..138be8b930faf2d4480a90af4aba7f2cbc8e3efc 100644 --- a/production/templates/production/_production_stream_card.html +++ b/production/templates/production/_production_stream_card.html @@ -1,35 +1,32 @@ {% load bootstrap %} {% load scipost_extras %} +<div class="w-100"> + {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %} +</div> <div class="card-block"> - {% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %} <div class="row"> - <div class="col-7"> + <div class="{% if form %}col-lg-7{% else %}col-12{% endif %}"> <h3>Events</h3> - <ul> - {% for event in stream.productionevent_set.all %} - {% include 'production/_production_event_li.html' with event=event %} - {% empty %} - <li>No events were found.</li> - {% endfor %} - </ul> + {% include 'production/_production_events.html' with events=stream.productionevent_set.all %} <br/> + {% if stream.total_duration %} - <h3>Total duration for this stream: {{ stream.total_duration|duration }}</h3> + <h3>Total duration for this stream: {{ stream.total_duration|duration }}</h3> {% endif %} {% if perms.scipost.can_publish_accepted_submission %} - <h3><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></h3> + <h3><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></h3> {% endif %} </div> {% if form %} - <div class="col-5"> - <h3>Add an event to this production stream:</h3> - <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input type="submit" name="submit" value="Submit"> - </form> - </div> + <div class="col-lg-5 mt-4 mt-lg-0"> + <h3>Add an event to this production stream:</h3> + <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-secondary" name="submit" value="Submit"> + </form> + </div> {% endif %} </div> </div> diff --git a/production/templates/production/completed.html b/production/templates/production/completed.html index 1cd7059cd206c97570a179c2e4c623619db68dd4..0bb839176ee0a6c60ac90c45daa0f6910a424979 100644 --- a/production/templates/production/completed.html +++ b/production/templates/production/completed.html @@ -1,18 +1,32 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Completed production streams</span> +{% endblock %} {% load bootstrap %} {% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Completed production streams</h1> + </div> +</div> <div class="row"> <div class="col-12"> - <h3>Completed production streams</h3> <ul class="list-group list-group-flush"> {% for stream in streams %} - <li class="list-group-item"> - {% include 'production/_production_stream_card.html' with stream=stream %} - </li> - <hr/> + <li class="list-group-item"> + <div class="w-100">{% include 'submissions/_submission_card_content_sparse.html' with submission=stream.submission %}</div> + <div class="card-block"> + <h3>Events</h3> + {% include 'production/_production_events.html' with events=stream.productionevent_set.all %} + </div> + </li> + {% empty %} + <li class="list-group-item">No completed production streams found.</li> {% endfor %} </ul> </div> diff --git a/production/templates/production/production.html b/production/templates/production/production.html index 36b7dcf39eccbc0c9982f5ebe5703962d4955682..21f99291149885c7f6845ac76e8896d544decebd 100644 --- a/production/templates/production/production.html +++ b/production/templates/production/production.html @@ -1,18 +1,28 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Production page</span> +{% endblock %} {% load bootstrap %} {% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Manuscripts under production</h1> + </div> +</div> <div class="row"> <div class="col-12"> - <h3>Manuscripts under production</h3> <ul class="list-group list-group-flush"> {% for stream in streams %} - <li class="list-group-item"> - {% include 'production/_production_stream_card.html' with stream=stream form=prodevent_form %} - </li> - <hr/> + <li class="list-group-item{% if not forloop.first %} pt-3{% endif %}"> + {% include 'production/_production_stream_card.html' with stream=stream form=prodevent_form %} + </li> + {% empty %} + <li class="list-group-item">No production streams found.</li> {% endfor %} </ul> </div> diff --git a/production/views.py b/production/views.py index 52363947aa75203c2957cd7a5a1cb920553275ad..abbbe447064714199d5f7c31a244618a3f568f25 100644 --- a/production/views.py +++ b/production/views.py @@ -1,15 +1,14 @@ +from django.contrib import messages 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 .constants import PRODUCTION_STREAM_COMPLETED +from .models import ProductionStream from .forms import ProductionEventForm -from submissions.models import Submission - ###################### # Production process # @@ -21,7 +20,7 @@ def production(request): Overview page for the production process. All papers with accepted but not yet published status are included here. """ - streams = ProductionStream.objects.filter(status='ongoing').order_by('opened') + streams = ProductionStream.objects.ongoing().order_by('opened') prodevent_form = ProductionEventForm() context = { 'streams': streams, @@ -35,40 +34,29 @@ def completed(request): """ Overview page for closed production streams. """ - streams = ProductionStream.objects.filter(status='completed').order_by('-opened') - context = {'streams': streams,} + streams = ProductionStream.objects.completed().order_by('-opened') + context = {'streams': streams} return render(request, 'production/completed.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}) + stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) + prodevent_form = ProductionEventForm(request.POST or None) + if prodevent_form.is_valid(): + prodevent = prodevent_form.save(commit=False) + prodevent.stream = stream + prodevent.noted_by = request.user.contributor + prodevent.save() else: - errormessage = 'This view can only be posted to.' - return render(request, 'scipost/error.html', {'errormessage': errormessage}) + messages.warning(request, 'The form was invalidly filled.') + return redirect(reverse('production:production')) -@permission_required('scipost.can_view_production', return_403=True) -@transaction.atomic +@permission_required('scipost.can_publish_accepted_submission', return_403=True) def mark_as_completed(request, stream_id): - stream = get_object_or_404(ProductionStream, pk=stream_id) - stream.status = 'completed' + stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) + stream.status = PRODUCTION_STREAM_COMPLETED stream.closed = timezone.now() stream.save() return redirect(reverse('production:production')) diff --git a/submissions/views.py b/submissions/views.py index fbacb1185dc49a84233c28c8b83ab8c24e3cd59a..72495253d1315392388c4f78cf25d399a56c4a6f 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -1233,8 +1233,7 @@ def fix_College_decision(request, rec_id): # Publish as Tier I, II or III recommendation.submission.status = 'accepted' # Create a ProductionStream object - prodstream = ProductionStream(submission=recommendation.submission, - opened=timezone.now()) + prodstream = ProductionStream(submission=recommendation.submission) prodstream.save() elif recommendation.recommendation == -3: # Reject