diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html index ac9e3efc8ba0ad5ddb719dbb080aa0fda5cb936f..8152c57481fd30234e3a95cd96c3e064a26bdd28 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html @@ -2,7 +2,7 @@ {% if form.fields.invitations_officer.choices|length > 0 %} <div id="productionstream-{{ stream.id }}-update-invitations_officer"> - <form hx-post="{% url 'production:update_invitations_officer' stream.id %}" + <form hx-post="{% url 'production:_hx_update_invitations_officer' stream.id %}" hx-target="#productionstream-{{ stream.id }}-update-invitations_officer" hx-swap="outerHTML" class="row mb-0"> diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_officer.html b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html index 2efbd7f2ebb9f75e1f2e64f85e11ed8f0248086d..014d8347a20bd1184865ce74b261861019455a66 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_officer.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html @@ -2,7 +2,7 @@ {% if form.fields.officer.choices|length > 0 %} <div id="productionstream-{{ stream.id }}-update-officer"> - <form hx-post="{% url 'production:update_officer' stream.id %}" + <form hx-post="{% url 'production:_hx_update_officer' stream.id %}" hx-target="#productionstream-{{ stream.id }}-update-officer" hx-swap="outerHTML" class="row"> diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_status.html b/scipost_django/production/templates/production/_hx_productionstream_change_status.html index 874111480ad50645cf1518a02f55608d585e0069..e2358f8ed2ac967612f05ed38c67dab6a3783296 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_status.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_status.html @@ -2,7 +2,7 @@ {% if form.fields.status.choices|length > 0 %} <form id="productionstream-{{ stream.id }}-update-status" - hx-post="{% url 'production:update_status' stream.id %}" + hx-post="{% url 'production:_hx_update_status' stream.id %}" hx-target="this" hx-swap="outerHTML" hx-trigger="submit" diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html index 4ce3afd863abb8ac1a9ece6f79340083938592c1..2a0aba9c9afb93535c0230dc98aa0366be4ecf3e 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html @@ -2,7 +2,7 @@ {% if form.fields.supervisor.choices|length > 0 %} <div id="productionstream-{{ stream.id }}-update-supervisor"> - <form hx-post="{% url 'production:update_supervisor' stream.id %}" + <form hx-post="{% url 'production:_hx_update_supervisor' stream.id %}" hx-target="#productionstream-{{ stream.id }}-update-supervisor" hx-swap="outerHTML" class="row"> diff --git a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html index 7220bc147d89d26b85daa2a1ad624d1665c8ac7c..476f155e9bb58018655ec51545291e1433e249ed 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html +++ b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html @@ -74,7 +74,7 @@ data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion"> <div id="productionstream-{{ productionstream.id }}-upload-proofs-body" class="accordion-body" - hx-get="{% url 'production:upload_proofs' stream_id=productionstream.id %}" + hx-get="{% url 'production:_hx_upload_proofs' stream_id=productionstream.id %}" hx-trigger="intersect once"> {% comment %} Placeholder before HTMX content loads {% endcomment %} @@ -175,7 +175,7 @@ <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"> <div class="row m-0 d-none-empty"> <button class="btn btn-warning text-white" - hx-get="{% url 'production:mark_as_completed' stream_id=productionstream.id %}" + hx-get="{% url 'production:_hx_mark_as_completed' stream_id=productionstream.id %}" {% if productionstream.status != 'published' %}hx-confirm="Are you sure you want to mark this unpublished stream as completed?"{% endif %} hx-target="#productionstream-{{ productionstream.id }}-details"> Mark this stream as completed diff --git a/scipost_django/production/templates/production/_hx_upload_proofs.html b/scipost_django/production/templates/production/_hx_upload_proofs.html new file mode 100644 index 0000000000000000000000000000000000000000..a60d1c2c6d8dc18ed091141455fd55e6b594d9a2 --- /dev/null +++ b/scipost_django/production/templates/production/_hx_upload_proofs.html @@ -0,0 +1,27 @@ +{% load bootstrap %} + + +<h3>Proofs</h3> +<div class="accordion" + id="productionstream-{{ stream.id }}-proofs-list-accordion"> + {% for proofs in stream.proofs.all %} + {% include 'production/_hx_productionstream_actions_proofs_item.html' with i_proof=forloop.counter0|add:1 active_id=total_proofs stream=stream proofs=proofs %} + {% empty %} + <div>No Proofs found.</div> + {% endfor %} +</div> + +<div class="row mt-3"> + <div class="col-12"> + <form enctype="multipart/form-data" + hx-post="{% url 'production:_hx_upload_proofs' stream_id=stream.id %}" + hx-target="#productionstream-{{ stream.id }}-upload-proofs-body"> + {% csrf_token %} + {{ form|bootstrap_purely_inline }} + <input type="submit" + class="btn btn-primary proof-action-button" + name="submit" + value="Upload"> + </form> + </div> +</div> diff --git a/scipost_django/production/templates/production/_production_stream_card.html b/scipost_django/production/templates/production/_production_stream_card.html index a7fbb4c1edbb1ae01a67d828c1bc3bc699999999..2b4430a24ae092adb1c7a641d336dffe57d07f0e 100644 --- a/scipost_django/production/templates/production/_production_stream_card.html +++ b/scipost_django/production/templates/production/_production_stream_card.html @@ -88,7 +88,7 @@ <li> <button type="button" class="btn btn-link" data-bs-toggle="toggle" data-bs-target="#upload_proofs">Upload Proofs</button> <div id="upload_proofs" style="display: none;"> - <form class="my-3" action="{% url 'production:upload_proofs' stream_id=stream.id %}" method="post" enctype="multipart/form-data"> + <form class="my-3" action="{% url 'production:_hx_upload_proofs' stream_id=stream.id %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ upload_proofs_form|bootstrap_inline }} <input type="submit" class="btn btn-outline-primary" name="submit" value="Upload"> diff --git a/scipost_django/production/templates/production/upload_proofs.html b/scipost_django/production/templates/production/upload_proofs.html index afa710d9b9a743eb84cffd2967076dac34d7b02a..16c6c905ebab596a3884f0665cd1cec92792efcf 100644 --- a/scipost_django/production/templates/production/upload_proofs.html +++ b/scipost_django/production/templates/production/upload_proofs.html @@ -1,27 +1,32 @@ -{% load bootstrap %} +{% extends 'production/base.html' %} +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Upload Proofs</span> +{% endblock %} -<h3>Proofs</h3> -<div class="accordion" - id="productionstream-{{ stream.id }}-proofs-list-accordion"> - {% for proofs in stream.proofs.all %} - {% include 'production/_hx_productionstream_actions_proofs_item.html' with i_proof=forloop.counter0|add:1 active_id=total_proofs stream=stream proofs=proofs %} - {% empty %} - <div>No Proofs found.</div> - {% endfor %} -</div> +{% load bootstrap %} -<div class="row mt-3"> - <div class="col-12"> - <form enctype="multipart/form-data" - hx-post="{% url 'production:upload_proofs' stream_id=stream.id %}" - hx-target="#productionstream-{{ stream.id }}-upload-proofs-body"> - {% csrf_token %} - {{ form|bootstrap_purely_inline }} - <input type="submit" - class="btn btn-primary proof-action-button" - name="submit" - value="Upload"> - </form> +{% block content %} + + <div class="row"> + <div class="col-12"> + <h1 class="highlight">Upload Proofs</h1> + {% include 'submissions/_submission_card_content.html' with submission=stream.submission %} + </div> + </div> + <div class="row"> + <div class="col-12"> + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" + class="btn btn-outline-secondary" + name="submit" + value="Upload"> + </form> + </ul> </div> </div> + +{% endblock content %} diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py index 39a748173a08a0b7aa5781b9093f3367b5e88023..54b32e500a26161a0ccefe456f6d2454c9d2f932 100644 --- a/scipost_django/production/urls.py +++ b/scipost_django/production/urls.py @@ -139,6 +139,11 @@ urlpatterns = [ [ path("", production_views.stream, name="stream"), path("status", production_views.update_status, name="update_status"), + path( + "_hx_status", + production_views._hx_update_status, + name="_hx_update_status", + ), path( "proofs/", include( @@ -148,6 +153,11 @@ urlpatterns = [ production_views.upload_proofs, name="upload_proofs", ), + path( + "_hx_upload", + production_views._hx_upload_proofs, + name="_hx_upload_proofs", + ), path( "<int:version>/", include( @@ -214,8 +224,8 @@ urlpatterns = [ ), path( "update", - production_views.update_officer, - name="update_officer", + production_views._hx_update_officer, + name="_hx_update_officer", ), ] ), @@ -236,8 +246,8 @@ urlpatterns = [ ), path( "update", - production_views.update_invitations_officer, - name="update_invitations_officer", + production_views._hx_update_invitations_officer, + name="_hx_update_invitations_officer", ), ] ), @@ -258,12 +268,17 @@ urlpatterns = [ ), path( "update", - production_views.update_supervisor, - name="update_supervisor", + production_views._hx_update_supervisor, + name="_hx_update_supervisor", ), ] ), ), + path( + "_hx_mark_completed", + production_views._hx_mark_as_completed, + name="_hx_mark_as_completed", + ), path( "mark_completed", production_views.mark_as_completed, diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py index 5463f01e3247ce4dad490b65957ee2f12d0f41c1..5cbc76c0833c4354ead98dfd0cf21bbd2836aeb5 100644 --- a/scipost_django/production/views.py +++ b/scipost_django/production/views.py @@ -451,6 +451,29 @@ def _hx_productionstream_actions_work_log(request, productionstream_id): ) +is_production_user() + + +@permission_required( + "scipost.can_take_decisions_related_to_proofs", raise_exception=True +) +def update_status(request, stream_id): + stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) + checker = ObjectPermissionChecker(request.user) + if not checker.has_perm("can_perform_supervisory_actions", stream): + return redirect(reverse("production:production", args=(stream.id,))) + + p = request.user.production_user + form = StreamStatusForm(request.POST or None, instance=stream, production_user=p) + + if form.is_valid(): + stream = form.save() + messages.warning(request, "Production Stream succesfully changed status.") + else: + messages.warning(request, "The status change was invalid.") + return redirect(stream.get_absolute_url()) + + @is_production_user() @permission_required_htmx( ( @@ -460,7 +483,7 @@ def _hx_productionstream_actions_work_log(request, productionstream_id): message="You do not have permission to update the status of this stream.", css_class="row", ) -def update_status(request, stream_id): +def _hx_update_status(request, stream_id): productionstream = get_object_or_404( ProductionStream.objects.ongoing(), pk=stream_id ) @@ -568,7 +591,7 @@ def remove_officer(request, stream_id, officer_id): css_class="row", ) @transaction.atomic -def update_officer(request, stream_id): +def _hx_update_officer(request, stream_id): productionstream = get_object_or_404( ProductionStream.objects.ongoing(), pk=stream_id ) @@ -738,7 +761,7 @@ def remove_invitations_officer(request, stream_id, officer_id): css_class="row", ) @transaction.atomic -def update_invitations_officer(request, stream_id): +def _hx_update_invitations_officer(request, stream_id): productionstream = get_object_or_404( ProductionStream.objects.ongoing(), pk=stream_id ) @@ -882,7 +905,7 @@ class UpdateEventView(UpdateView): css_class="row", ) @transaction.atomic -def update_supervisor(request, stream_id): +def _hx_update_supervisor(request, stream_id): productionstream = get_object_or_404( ProductionStream.objects.ongoing(), pk=stream_id ) @@ -969,7 +992,7 @@ class DeleteEventView(DeleteView): @is_production_user() @permission_required("scipost.can_publish_accepted_submission", raise_exception=True) @transaction.atomic -def mark_as_completed(request, stream_id): +def _hx_mark_as_completed(request, stream_id): productionstream = get_object_or_404( ProductionStream.objects.ongoing(), pk=stream_id ) @@ -997,6 +1020,26 @@ def mark_as_completed(request, stream_id): ) +@is_production_user() +@permission_required("scipost.can_publish_accepted_submission", raise_exception=True) +@transaction.atomic +def mark_as_completed(request, stream_id): + stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) + stream.status = constants.PRODUCTION_STREAM_COMPLETED + stream.closed = timezone.now() + stream.save() + + prodevent = ProductionEvent( + stream=stream, + event="status", + comments=" marked the Production Stream as completed.", + noted_by=request.user.production_user, + ) + prodevent.save() + messages.success(request, "Stream marked as completed.") + return redirect(reverse("production:production")) + + @is_production_user() @permission_required_htmx( ( @@ -1006,7 +1049,7 @@ def mark_as_completed(request, stream_id): message="You cannot upload proofs for this stream.", ) @transaction.atomic -def upload_proofs(request, stream_id): +def _hx_upload_proofs(request, stream_id): """ Called by a member of the Production Team. Upload the production version .pdf of a submission. @@ -1044,6 +1087,51 @@ def upload_proofs(request, stream_id): prodevent.save() context = {"stream": stream, "form": form, "total_proofs": stream.proofs.count()} + return render(request, "production/_hx_upload_proofs.html", context) + + +@is_production_user() +@permission_required("scipost.can_upload_proofs", raise_exception=True) +@transaction.atomic +def upload_proofs(request, stream_id): + """ + Called by a member of the Production Team. + Upload the production version .pdf of a submission. + """ + stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id) + checker = ObjectPermissionChecker(request.user) + if not checker.has_perm("can_work_for_stream", stream): + return redirect(reverse("production:production")) + + form = ProofsUploadForm(request.POST or None, request.FILES or None) + if form.is_valid(): + 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 + if stream.status == constants.PROOFS_TASKED: + stream.status = constants.PROOFS_PRODUCED + stream.save() + elif stream.status == constants.PROOFS_RETURNED: + stream.status = constants.PROOFS_CORRECTED + stream.save() + + prodevent = ProductionEvent( + stream=stream, + event="status", + comments="New Proofs uploaded, version {v}".format(v=proofs.version), + noted_by=request.user.production_user, + ) + prodevent.save() + return redirect(stream.get_absolute_url()) + + context = {"stream": stream, "form": form} return render(request, "production/upload_proofs.html", context)