diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html b/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html new file mode 100644 index 0000000000000000000000000000000000000000..2062f3fdffc425454229f50acfb1cd0bcd5dee2e --- /dev/null +++ b/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html @@ -0,0 +1,47 @@ +{% load bootstrap %} +{% load scipost_extras %} + +{% comment %} {% if status_form|all_fields_have_choices %} + <form id="productionstream-{{ productionstream.id }}-update-status" hx-post="{% url 'production:update_status' productionstream.id %}" hx-target="this" hx-swap="outerHTML" hx-trigger="submit"> + {% csrf_token %} + <div class="row"> + <div class="col">{{ status_form|bootstrap_purely_inline }}</div> + <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty" hx-post="{% url 'production:render_action_buttons' productionstream.id 'status' %}" hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ productionstream.id }}_id_status" hx-target="this"></div> + </div> + </form> +{% endif %} + +{% if supervisor_form|all_fields_have_choices %} + <form id="productionstream-{{ productionstream.id }}-update-supervisor" hx-post="{% url 'production:update_supervisor' productionstream.id %}" hx-target="this" hx-swap="outerHTML" hx-trigger="submit"> + {% csrf_token %} + <div class="row"> + <div class="col">{{ supervisor_form|bootstrap_purely_inline }}</div> + <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty" hx-post="{% url 'production:render_action_buttons' productionstream.id 'supervisor' %}" hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ productionstream.id }}_id_supervisor" hx-target="this"></div> + </div> + </form> +{% endif %} + +{% if officer_form|all_fields_have_choices %} + <form id="productionstream-{{ productionstream.id }}-update-officer" hx-post="{% url 'production:update_officer' productionstream.id %}" hx-target="this" hx-swap="outerHTML" hx-trigger="submit"> + {% csrf_token %} + <div class="row"> + <div class="col">{{ officer_form|bootstrap_purely_inline }}</div> + <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty" hx-post="{% url 'production:render_action_buttons' productionstream.id 'officer' %}" hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ productionstream.id }}_id_officer" hx-target="this"></div> + </div> + </form> +{% endif %} + +{% if invitations_officer_form|all_fields_have_choices %} + <form id="productionstream-{{ productionstream.id }}-update-invitations_officer" hx-post="{% url 'production:update_invitations_officer' productionstream.id %}" hx-target="this" hx-swap="outerHTML" hx-trigger="submit"> + {% csrf_token %} + <div class="row"> + <div class="col">{{ invitations_officer_form|bootstrap_purely_inline }}</div> + <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty" hx-post="{% url 'production:render_action_buttons' productionstream.id 'invitations_officer' %}" hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ productionstream.id }}_id_invitations_officer" hx-target="this"></div> + </div> + </form> +{% endif %} {% endcomment %} + +{% include "production/_hx_productionstream_change_status.html" with form=status_form stream=productionstream %} +{% include "production/_hx_productionstream_change_supervisor.html" with form=supervisor_form stream=productionstream %} +{% include "production/_hx_productionstream_change_officer.html" with form=officer_form stream=productionstream %} +{% include "production/_hx_productionstream_change_invitations_officer.html" with form=invitations_officer_form stream=productionstream %} diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_invofficer.html b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html similarity index 80% rename from scipost_django/production/templates/production/_hx_productionstream_change_invofficer.html rename to scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html index 90d327d97b95429e34368ac3c4542bf8f2fc61fb..c2ec64a40630462d8040c40f4f7021840762f1b8 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_invofficer.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html @@ -13,7 +13,13 @@ hx-swap="innerHTML" hx-trigger="change from:select#productionstream_{{ stream.id }}_id_invitations_officer" hx-target="this"></div> - <div class="text-primary">{{ message }}</div> </form> </div> {% endif %} + +{% if message %} + <div id="productionstream-{{ stream.id }}-actions-message" + class="alert alert-{{ message.type }}" + role="alert" + hx-swap-oob="true">{{ message.text }}</div> +{% endif %} diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_prodofficer.html b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html similarity index 79% rename from scipost_django/production/templates/production/_hx_productionstream_change_prodofficer.html rename to scipost_django/production/templates/production/_hx_productionstream_change_officer.html index 830e714301723b7e033adea90ce89155ecacb531..869ab3255b2b0a6cd7f56f9d16986ddf59bdd650 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_prodofficer.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html @@ -13,7 +13,13 @@ hx-swap="innerHTML" hx-trigger="change from:select#productionstream_{{ stream.id }}_id_officer" hx-target="this"></div> - <div class="text-primary">{{ message }}</div> </form> </div> {% endif %} + +{% if message %} + <div id="productionstream-{{ stream.id }}-actions-message" + class="alert alert-{{ message.type }}" + role="alert" + hx-swap-oob="true">{{ message.text }}</div> +{% endif %} 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 d90daf46f4c0b6e11d6fccf78fafa0519395fc17..c7bcde48f7f5dd09b73e374216777d08687a196f 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_status.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_status.html @@ -1,12 +1,12 @@ {% load bootstrap %} {% if form.fields.status.choices|length > 0 %} - <form id="productionstream-{{ stream.id }}-update-status" hx-post="{% url 'production:update_status' stream.id %}" hx-target="this" hx-swap="outerHTML" - hx-trigger="submit, submit from:#productionstream-{{ productionstream.id }}-details target:form delay:500"> + hx-trigger="submit" + hx-sync="this:replace"> {% csrf_token %} <div class="row"> <div class="col">{{ form|bootstrap_purely_inline }}</div> @@ -15,7 +15,13 @@ hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ stream.id }}_id_status" hx-target="this"></div> - <div class="text-primary">{{ message }}</div> </div> </form> {% endif %} + +{% if message %} + <div id="productionstream-{{ stream.id }}-actions-message" + class="alert alert-{{ message.type }}" + role="alert" + hx-swap-oob="true">{{ message.text }}</div> +{% endif %} 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 8fdb90c98acac45ad06e84f00a6dcd3f68873843..e7c8743a7dd5063236dada698c9a3e4d61cf77e1 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html +++ b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html @@ -13,7 +13,13 @@ hx-swap="innerHTML" hx-trigger="load, change from:select#productionstream_{{ stream.id }}_id_supervisor" hx-target="this"></div> - <div class="text-primary">{{ message }}</div> </form> </div> {% endif %} + +{% if message %} + <div id="productionstream-{{ stream.id }}-actions-message" + class="alert alert-{{ message.type }}" + role="alert" + hx-swap-oob="true">{{ message.text }}</div> +{% endif %} 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 4832b4829521da96b08e8b5a5409955f8af65dd1..36d96074600a0c53c1fb07530e282cd3da59b450 100644 --- a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html +++ b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html @@ -8,7 +8,7 @@ </p> <div class="row"> - <div class="col-12 col-md-6"> + <div class="col-12 col-md-6 mb-3"> <div class="accordion px-2" id="productionstream-{{ productionstream.id }}-actions-accordion"> @@ -17,26 +17,26 @@ {% if perms.scipost.can_take_decisions_related_to_proofs %} <div class="accordion-item"> <h2 class="accordion-header" - id="productionstream-{{ productionstream.id }}-change-stream-property-header"> + id="productionstream-{{ productionstream.id }}-change-stream-properties-header"> <button class="accordion-button fs-6 collapsed" type="button" data-bs-toggle="collapse" - data-bs-target="#productionstream-{{ productionstream.id }}-change-stream-property" + data-bs-target="#productionstream-{{ productionstream.id }}-change-stream-properties" aria-expanded="true" - aria-controls="productionstream-{{ productionstream.id }}-change-stream-property"> - Change stream property + aria-controls="productionstream-{{ productionstream.id }}-change-stream-properties" + hx-get="{% url 'production:_hx_productionstream_actions_change_properties' productionstream_id=productionstream.id %}" + hx-trigger="click once, submit from:#productionstream-{{ productionstream.id }}-details-contents target:form delay:1000" + hx-target="#productionstream-{{ productionstream.id }}-change-stream-properties-body"> + Change stream properties </button> </h2> - <div id="productionstream-{{ productionstream.id }}-change-stream-property" + <div id="productionstream-{{ productionstream.id }}-change-stream-properties" class="accordion-collapse collapse" - aria-labelledby="productionstream-{{ productionstream.id }}-change-stream-property-header" + aria-labelledby="productionstream-{{ productionstream.id }}-change-stream-properties-header" data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion"> - <div id="productionstream-{{ productionstream.id }}-change-stream-property-body" - class="accordion-body"> - {% include "production/_hx_productionstream_change_status.html" with form=status_form stream=productionstream %} - {% include "production/_hx_productionstream_change_supervisor.html" with form=supervisor_form stream=productionstream %} - {% include "production/_hx_productionstream_change_prodofficer.html" with form=prod_officer_form stream=productionstream %} - {% include "production/_hx_productionstream_change_invofficer.html" with form=inv_officer_form stream=productionstream %} + <div class="accordion-body"> + <div id="productionstream-{{ productionstream.id }}-change-stream-properties-body"></div> + <div id="productionstream-{{ productionstream.id }}-actions-message"></div> </div> </div> </div> @@ -162,7 +162,7 @@ </div> <div id="productionstream-{{ productionstream.id }}-event-container" - class="col-12 col-md-6 d-flex flex-column"> + class="col-12 col-md-6 d-flex flex-column px-3"> {% comment %} This might be better to refactor with an OOB response on each event addition {% endcomment %} <h3>Events</h3> <div id="productionstream-{{ productionstream.id }}-event-list" diff --git a/scipost_django/production/templates/production/upload_proofs.html b/scipost_django/production/templates/production/upload_proofs.html index 7b20db78270cf4f7125c5a5be1fd297e423f64e8..01ca25458692607633deba709a67c58c82576c7e 100644 --- a/scipost_django/production/templates/production/upload_proofs.html +++ b/scipost_django/production/templates/production/upload_proofs.html @@ -2,9 +2,78 @@ <h3>Proofs</h3> -<ul> +<div class="accordion" + id="productionstream-{{ stream.id }}-proofs-list-accordion"> {% for proofs in stream.proofs.all %} - <li class="py-1"> + <div class="accordion-item"> + <h4 class="accordion-header"> + <button class="accordion-button {% if forloop.counter0|add:1 != total_proofs %}collapsed{% endif %}" + type="button" + data-bs-toggle="collapse" + data-bs-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ forloop.counter0 }}" + aria-expanded="false" + aria-controls="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ forloop.counter0 }}"> + <div class="row w-100 m-0 pe-2 align-items-center"> + <div class="col-6 col-sm col-lg-6 col-xl fs-6">Version {{ proofs.version }}</div> + <div class="col-6 col-sm-auto col-md-12 col-lg-6 col-xl-auto">{{ proofs.created|date:"DATE_FORMAT" }}</div> + <div class="col-12 col-sm-auto badge bg-secondary">{{ proofs.get_status_display|title }}</div> + </div> + </button> + </h4> + <div id="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ forloop.counter0 }}" + class="accordion-collapse collapse {% if forloop.counter0|add:1 == total_proofs %}show{% endif %}" + data-bs-parent="#productionstream-{{ stream.id }}-proofs-list-accordion"> + <div class="accordion-body"> + <div class="row"> + <div class="col">Uploaded by:</div> + <div class="col-auto">{{ proofs.uploaded_by.user.first_name }} {{ proofs.uploaded_by.user.last_name }}</div> + </div> + <div class="row"> + <div class="col">Accessible for authors:</div> + <div class="col-auto"> + {{ proofs.accessible_for_authors|yesno:'<strong>Yes</strong>,No'|safe }} + </div> + </div> + + {% comment %} Buttons {% endcomment %} + <div class="row g-2"> + <div class="col-12 col-sm-6 col-md-12 col-lg-6 h-100 d-none-empty"> + <div class="row m-0 d-none-empty"> + <button class="btn btn-sm btn-secondary" + hx-get="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}"> + Download Proofs + </button> + </div> + </div> + {% if proofs.status == 'uploaded' %} + <div class="col-6 col-sm-3 col-md-6 col-lg-3 h-100 d-none-empty"> + <div class="row m-0 d-none-empty"> + <button class="btn btn-sm btn-primary">Accept</button> + </div> + </div> + <div class="col-6 col-sm-3 col-md-6 col-lg-3 h-100 d-none-empty"> + <div class="row m-0 d-none-empty"> + <button class="btn btn-sm btn-danger">Decline</button> + </div> + </div> + </div> + {% elif proofs.status == 'accepted_sup' %} + <div class="col-12 col-sm-6 col-md-12 col-lg-6 h-100 d-none-empty"> + <div class="row m-0 d-none-empty"> + <button class="btn btn-sm btn-warning" + hx-get="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}"> + Send proofs to authors + </button> + </div> + </div> + {% endif %} + + </div> + </div> + </div> + + + {% comment %} <li class="py-1"> <a href="{% url 'production:proofs' stream_id=stream.id version=proofs.version %}">Version {{ proofs.version }}</a> · <span class="label label-secondary label-sm">{{ proofs.get_status_display }}</span> <br> Uploaded by: {{ proofs.uploaded_by.user.first_name }} {{ proofs.uploaded_by.user.last_name }} @@ -29,13 +98,13 @@ </ul> {% endif %} {% endif %} - </li> + </li> {% endcomment %} {% empty %} - <li>No Proofs found.</li> + <div>No Proofs found.</div> {% endfor %} -</ul> +</div> -<div class="row"> +<div class="row mt-3"> <div class="col-12"> <form method="post" enctype="multipart/form-data"> {% csrf_token %} diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py index bca560a711e4f1e45728d6cf981e1740a5915ce8..f2458a89353cadea783a8de1761ff785832cdf0b 100644 --- a/scipost_django/production/urls.py +++ b/scipost_django/production/urls.py @@ -29,6 +29,11 @@ urlpatterns = [ production_views._hx_productionstream_details_contents, name="_hx_productionstream_details_contents", ), + path( + "actions_change_properties", + production_views._hx_productionstream_actions_change_properties, + name="_hx_productionstream_actions_change_properties", + ), path( "events/", include( diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py index 78f85233ec4c968002aebd6d96b1e7364c87fc51..289324ee71e0db6c8623018baf51cf537e05bca2 100644 --- a/scipost_django/production/views.py +++ b/scipost_django/production/views.py @@ -107,7 +107,7 @@ def _hx_productionstream_details_contents(request, productionstream_id): upload_proofs_form = ProofsUploadForm() context = { - "" "productionstream": productionstream, + "productionstream": productionstream, "status_form": status_form, "supervisor_form": supervisor_form, "inv_officer_form": inv_officer_form, @@ -121,6 +121,42 @@ def _hx_productionstream_details_contents(request, productionstream_id): ) +@is_production_user() +@permission_required("scipost.can_view_production", raise_exception=True) +def _hx_productionstream_actions_change_properties(request, productionstream_id): + productionstream = get_object_or_404(ProductionStream, pk=productionstream_id) + status_form = StreamStatusForm( + instance=productionstream, + production_user=request.user.production_user, + auto_id=f"productionstream_{productionstream.id}_id_%s", + ) + supervisor_form = AssignSupervisorForm( + instance=productionstream, + auto_id=f"productionstream_{productionstream.id}_id_%s", + ) + invitations_officer_form = AssignInvitationsOfficerForm( + instance=productionstream, + auto_id=f"productionstream_{productionstream.id}_id_%s", + ) + officer_form = AssignOfficerForm( + instance=productionstream, + auto_id=f"productionstream_{productionstream.id}_id_%s", + ) + + context = { + "productionstream": productionstream, + "status_form": status_form, + "supervisor_form": supervisor_form, + "officer_form": officer_form, + "invitations_officer_form": invitations_officer_form, + } + return render( + request, + "production/_hx_productionstream_actions_change_properties.html", + context, + ) + + @is_production_user() @permission_required("scipost.can_view_production", raise_exception=True) def _hx_event_form(request, productionstream_id, event_id=None): @@ -362,7 +398,10 @@ def update_status(request, stream_id): if not checker.has_perm("can_perform_supervisory_actions", productionstream): context = { "stream": productionstream, - "message": "You do not have permission to perform this action. ", + "message": { + "type": "danger", + "text": "You do not have permission to perform this action. ", + }, } else: status_form = StreamStatusForm( @@ -375,9 +414,16 @@ def update_status(request, stream_id): if status_form.is_valid(): stream = status_form.save() status_form.fields["status"].choices = status_form.get_available_statuses() - message = "Production Stream succesfully changed status." + message = { + "type": "success", + "text": "Production Stream succesfully changed status.", + } + else: - message = "\\n".join(status_form.errors.values()) + message = { + "type": "danger", + "text": "\\n".join(status_form.errors.values()), + } context = { "stream": productionstream, @@ -468,19 +514,22 @@ def update_officer(request, stream_id): "message": "You do not have permission to perform this action. ", } else: - prod_officer_form = AssignOfficerForm( + officer_form = AssignOfficerForm( request.POST or None, instance=productionstream, auto_id=f"productionstream_{productionstream.id}_id_%s", ) - if prod_officer_form.is_valid(): - prod_officer_form.save() - officer = prod_officer_form.cleaned_data.get("officer") + if officer_form.is_valid(): + officer_form.save() + officer = officer_form.cleaned_data.get("officer") # Add officer to stream if they exist. if officer is not None: assign_perm("can_work_for_stream", officer.user, productionstream) - message = f"Officer {officer} has been assigned." + message = { + "type": "success", + "text": f"Officer {officer} has been assigned.", + } event = ProductionEvent( stream=productionstream, @@ -499,20 +548,25 @@ def update_officer(request, stream_id): # Remove old officer. else: remove_perm("can_work_for_stream", prev_officer.user, productionstream) - message = f"Officer {prev_officer} has been removed." - + message = { + "type": "success", + "text": f"Officer {prev_officer} has been removed.", + } else: - message = "\\n".join(prod_officer_form.errors.values()) + message = { + "type": "danger", + "text": "\\n".join(officer_form.errors.values()), + } context = { "stream": productionstream, - "form": prod_officer_form, + "form": officer_form, "message": message, } return render( request, - "production/_hx_productionstream_change_prodofficer.html", + "production/_hx_productionstream_change_officer.html", context, ) @@ -608,22 +662,30 @@ def update_invitations_officer(request, stream_id): if not checker.has_perm("can_perform_supervisory_actions", productionstream): context = { "stream": productionstream, - "message": "You do not have permission to perform this action. ", + "message": { + "type": "danger", + "text": "You do not have permission to perform this action. ", + }, } else: - inv_officer_form = AssignInvitationsOfficerForm( + invitations_officer_form = AssignInvitationsOfficerForm( request.POST or None, instance=productionstream, auto_id=f"productionstream_{productionstream.id}_id_%s", ) - if inv_officer_form.is_valid(): - inv_officer_form.save() - inv_officer = inv_officer_form.cleaned_data.get("invitations_officer") + if invitations_officer_form.is_valid(): + invitations_officer_form.save() + inv_officer = invitations_officer_form.cleaned_data.get( + "invitations_officer" + ) # Add invitations officer to stream if they exist. if inv_officer is not None: assign_perm("can_work_for_stream", inv_officer.user, productionstream) - message = f"Invitations Officer {inv_officer} has been assigned." + message = { + "type": "success", + "text": f"Invitations Officer {inv_officer} has been assigned.", + } event = ProductionEvent( stream=productionstream, @@ -644,20 +706,26 @@ def update_invitations_officer(request, stream_id): remove_perm( "can_work_for_stream", prev_inv_officer.user, productionstream ) - message = f"Invitations Officer {prev_inv_officer} has been removed." + message = { + "type": "success", + "text": f"Invitations Officer {prev_inv_officer} has been removed.", + } else: - message = "\\n".join(inv_officer_form.errors.values()) + message = { + "type": "danger", + "text": "\\n".join(invitations_officer_form.errors.values()), + } context = { "stream": productionstream, - "form": inv_officer_form, + "form": invitations_officer_form, "message": message, } return render( request, - "production/_hx_productionstream_change_invofficer.html", + "production/_hx_productionstream_change_invitations_officer.html", context, ) @@ -756,7 +824,10 @@ def update_supervisor(request, stream_id): # Add supervisor to stream if they exist. if supervisor is not None: - message = f"Supervisor {supervisor} has been assigned." + message = { + "type": "success", + "text": f"Supervisor {supervisor} has been assigned.", + } assign_perm("can_work_for_stream", supervisor.user, productionstream) assign_perm( @@ -785,10 +856,16 @@ def update_supervisor(request, stream_id): prev_supervisor.user, productionstream, ) - message = f"Supervisor {supervisor} has been removed." + message = { + "type": "success", + "text": f"Supervisor {prev_supervisor} has been removed.", + } else: - message = "\\n".join(supervisor_form.errors.values()) + message = { + "type": "danger", + "text": "\\n".join(supervisor_form.errors.values()), + } context = { "stream": productionstream, @@ -843,6 +920,11 @@ def mark_as_completed(request, stream_id): ) production_event.save() + messages.success( + request, + "Production Stream has been marked as completed.", + ) + return HttpResponse( r"""<summary class="text-white bg-success summary-unstyled p-3"> Production Stream has been marked as completed. @@ -891,7 +973,7 @@ def upload_proofs(request, stream_id): prodevent.save() return redirect(stream.get_absolute_url()) - context = {"stream": stream, "form": form} + context = {"stream": stream, "form": form, "total_proofs": stream.proofs.count()} return render(request, "production/upload_proofs.html", context)