From 29273420050afb215c2f4f0927dccb6209801ab8 Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Tue, 20 Jun 2023 17:38:44 +0200
Subject: [PATCH] reformat proofs views to use htmx fix htmx mailer subview add
 appropriate permissions to stream view and proofs

---
 .../mails/templates/mails/_hx_mail_form.html  |   5 +-
 scipost_django/production/permissions.py      |  27 ++
 ...ctionstream_actions_change_properties.html |  17 +-
 ..._productionstream_actions_proofs_item.html |   8 +-
 ...tionstream_change_invitations_officer.html |   2 +-
 ..._hx_productionstream_details_contents.html | 286 +++++++++---------
 scipost_django/production/urls.py             |  15 +
 scipost_django/production/views.py            | 220 +++++++++++++-
 scipost_django/scipost/views.py               |   8 +-
 9 files changed, 413 insertions(+), 175 deletions(-)

diff --git a/scipost_django/mails/templates/mails/_hx_mail_form.html b/scipost_django/mails/templates/mails/_hx_mail_form.html
index f2f1bfa25..6e9ae47ee 100644
--- a/scipost_django/mails/templates/mails/_hx_mail_form.html
+++ b/scipost_django/mails/templates/mails/_hx_mail_form.html
@@ -1,9 +1,6 @@
 {% load bootstrap %}
 
-<h2 class="text-danger">HTMX send mail view is not working ATM.</h2>
-<form enctype="multipart/form-data"
-      method="post"
-      class="border border-danger p-2">
+<form hx-post="{{ view_url }}">
     {% csrf_token %}
     {% if transfer_data_form %}{{ transfer_data_form }}{% endif %}
     {{ form|bootstrap }}
diff --git a/scipost_django/production/permissions.py b/scipost_django/production/permissions.py
index 841dd9451..909d5b9a5 100644
--- a/scipost_django/production/permissions.py
+++ b/scipost_django/production/permissions.py
@@ -3,6 +3,9 @@ __license__ = "AGPL v3"
 
 
 from django.contrib.auth.decorators import user_passes_test
+from scipost.views import HTMXPermissionsDenied
+from functools import wraps
+from django.contrib import messages
 
 
 def is_production_user():
@@ -15,3 +18,27 @@ def is_production_user():
         return False
 
     return user_passes_test(test)
+
+
+def permission_required_htmx(
+    perm,
+    message="You do not have the required permissions.",
+    **message_kwargs,
+):
+    def decorator(view_func):
+        @wraps(view_func)
+        def _wrapped_view(request, *args, **kwargs):
+            if isinstance(perm, str):
+                perms = (perm,)
+            else:
+                perms = perm
+
+            if request.user.has_perms(perms):
+                return view_func(request, *args, **kwargs)
+            else:
+                messages.error(request, message)
+                return HTMXPermissionsDenied(message, **message_kwargs)
+
+        return _wrapped_view
+
+    return decorator
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
index 7afb65a41..d55ce237b 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html
@@ -1,7 +1,16 @@
 {% load bootstrap %}
 {% load scipost_extras %}
+{% load guardian_tags %}
 
-{% 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 %}
+{% get_obj_perms request.user for productionstream as "sub_perms" %}
+
+{% if "can_work_for_stream" in sub_perms and perms.scipost.can_take_decisions_related_to_proofs %}
+    {% include "production/_hx_productionstream_change_status.html" with form=status_form stream=productionstream %}
+{% endif %}
+{% if perms.scipost.can_assign_production_supervisor %}
+    {% include "production/_hx_productionstream_change_supervisor.html" with form=supervisor_form stream=productionstream %}
+{% endif %}
+{% if "can_work_for_stream" in sub_perms and perms.scipost.can_assign_production_officer %}
+    {% 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 %}
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html b/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html
index 6848c94ea..f9d1916ae 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html
@@ -42,7 +42,7 @@
                 {% 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 hx-get="{% url 'production:decision' proofs.stream.id proofs.version 'accept' %}"
+                            <button hx-get="{% url 'production:_hx_proofs_decision' proofs.stream.id proofs.version 'accept' %}"
                                     hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
                                     hx-swap="outerHTML"
                                     class="btn btn-sm btn-primary proof-action-button">Accept</button>
@@ -50,7 +50,7 @@
                     </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 hx-get="{% url 'production:decision' proofs.stream.id proofs.version 'decline' %}"
+                            <button hx-get="{% url 'production:_hx_proofs_decision' proofs.stream.id proofs.version 'decline' %}"
                                     hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
                                     hx-swap="outerHTML"
                                     class="btn btn-sm btn-danger proof-action-button">Decline</button>
@@ -59,7 +59,7 @@
                 {% 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 hx-get="{% url 'production:send_proofs' proofs.stream.id proofs.version %}"
+                            <button hx-get="{% url 'production:_hx_send_proofs' proofs.stream.id proofs.version %}"
                                     hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-action-row"
                                     hx-swap="afterend"
                                     class="btn btn-sm btn-warning">Send proofs to authors</button>
@@ -68,7 +68,7 @@
                 {% else %}
                     <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 hx-get="{% url 'production:toggle_accessibility' proofs.stream.id proofs.version %}"
+                            <button hx-get="{% url 'production:_hx_toggle_accessibility' proofs.stream.id proofs.version %}"
                                     hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
                                     hx-swap="outerHTML"
                                     class="btn btn-sm btn-primary">
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 7f6e4df72..f4c7284ff 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
@@ -5,7 +5,7 @@
         <form hx-post="{% url 'production:update_invitations_officer' stream.id %}"
               hx-target="#productionstream-{{ stream.id }}-update-invitations_officer"
               hx-swap="outerHTML"
-              class="row">
+              class="row mb-0">
             {% csrf_token %}
             <div class="col">{{ form|bootstrap_purely_inline }}</div>
             <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"
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 4d58bc797..0e4c81441 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
@@ -9,30 +9,30 @@
 </p>
 
 <div class="row">
-  <div class="col-12 col-md-6 d-flex flex-column">
+  {% if "can_work_for_stream" in sub_perms or perms.scipost.can_assign_production_supervisor %}
+    <div class="col-12 col-md d-flex flex-column">
  
-    <div class="accordion px-2 mb-2"
-         id="productionstream-{{ productionstream.id }}-actions-accordion">
-      <h3>Actions</h3>
+      <div class="accordion px-2 mb-2"
+           id="productionstream-{{ productionstream.id }}-actions-accordion">
+        <h3>Actions</h3>
 
-      {% if perms.scipost.can_take_decisions_related_to_proofs %}
-        <div class="accordion-item">
-          <h2 class="accordion-header"
-              id="productionstream-{{ productionstream.id }}-change-properties-header">
-            <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'change-properties' %}collapsed{% endif %}"
-                    type="button"
-                    data-bs-toggle="collapse"
-                    data-bs-target="#productionstream-{{ productionstream.id }}-change-properties"
-                    aria-expanded="true"
-                    aria-controls="productionstream-{{ productionstream.id }}-change-properties">
-              Change Properties
-            </button>
-          </h2>
-          <div id="productionstream-{{ productionstream.id }}-change-properties"
-               class="accordion-collapse collapse {% if accordion_default_open == 'change-properties' %}show{% endif %}"
-               aria-labelledby="productionstream-{{ productionstream.id }}-change-properties-header"
-               data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
-            <div class="accordion-body">
+        {% if perms.scipost.can_take_decisions_related_to_proofs %}
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-change-properties-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'change-properties' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-change-properties"
+                      aria-expanded="true"
+                      aria-controls="productionstream-{{ productionstream.id }}-change-properties">
+                Change Properties
+              </button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-change-properties"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'change-properties' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-change-properties-header"
+                 data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
               <div id="productionstream-{{ productionstream.id }}-change-properties-body"
                    class="accordion-body"
                    hx-get="{% url 'production:_hx_productionstream_actions_change_properties' productionstream_id=productionstream.id %}"
@@ -57,157 +57,156 @@
                 {% comment %} End placeholder {% endcomment %}
 
               </div>
-              <div id="productionstream-{{ productionstream.id }}-actions-message"></div>
             </div>
           </div>
-        </div>
-      {% endif %}
+        {% endif %}
 
-      {% if "can_work_for_stream" in sub_perms %}
-        <div class="accordion-item">
-          <h2 class="accordion-header"
-              id="productionstream-{{ productionstream.id }}-upload-proofs-header">
-            <button class="accordion-button fs-6 {% if accordion_default_open == '' or  accordion_default_open != 'upload_proofs' %}collapsed{% endif %}"
-                    type="button"
-                    data-bs-toggle="collapse"
-                    data-bs-target="#productionstream-{{ productionstream.id }}-upload-proofs"
-                    aria-expanded="false"
-                    aria-controls="productionstream-{{ productionstream.id }}-upload-proofs">Upload Proofs</button>
-          </h2>
-          <div id="productionstream-{{ productionstream.id }}-upload-proofs"
-               class="accordion-collapse collapse {% if accordion_default_open == 'upload-proofs' %}show{% endif %}"
-               aria-labelledby="productionstream-{{ productionstream.id }}-upload-proofs-header"
-               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-trigger="intersect once">
+        {% if "can_work_for_stream" in sub_perms %}
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-upload-proofs-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'upload-proofs' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-upload-proofs"
+                      aria-expanded="false"
+                      aria-controls="productionstream-{{ productionstream.id }}-upload-proofs">Upload Proofs</button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-upload-proofs"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'upload-proofs' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-upload-proofs-header"
+                 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-trigger="intersect once">
  
-              {% comment %} Placeholder before HTMX content loads {% endcomment %}
-              <div class="placeholder-glow">
-                <h3>
-                  <span class="placeholder">Proofs</span>
-                </h3>
-                <div class="w-100 mb-2 py-4 placeholder"></div>
-                <div class="w-100 mb-2 py-4 placeholder"></div>
-                <div class="row">
-                  <div class="col-3">
-                    <div class="w-100 py-3 placeholder"></div>
-                  </div>
-                  <div class="col-9">
-                    <div class="w-100 py-3 placeholder"></div>
+                {% comment %} Placeholder before HTMX content loads {% endcomment %}
+                <div class="placeholder-glow">
+                  <h3>
+                    <span class="placeholder">Proofs</span>
+                  </h3>
+                  <div class="w-100 mb-2 py-4 placeholder"></div>
+                  <div class="w-100 mb-2 py-4 placeholder"></div>
+                  <div class="row">
+                    <div class="col-3">
+                      <div class="w-100 py-3 placeholder"></div>
+                    </div>
+                    <div class="col-9">
+                      <div class="w-100 py-3 placeholder"></div>
+                    </div>
                   </div>
+                  <div class="w-25 px-1 py-3 placeholder"></div>
                 </div>
-                <div class="w-25 px-1 py-3 placeholder"></div>
-              </div>
-              {% comment %} End placeholder {% endcomment %}
+                {% comment %} End placeholder {% endcomment %}
 
+              </div>
             </div>
           </div>
-        </div>
-      {% endif %}
 
-      <div class="accordion-item">
-        <h2 class="accordion-header"
-            id="productionstream-{{ productionstream.id }}-work-log-header">
-          <button class="accordion-button fs-6 {% if accordion_default_open == '' or  accordion_default_open != 'work-log' %}collapsed{% endif %}"
-                  type="button"
-                  data-bs-toggle="collapse"
-                  data-bs-target="#productionstream-{{ productionstream.id }}-work-log"
-                  aria-expanded="false"
-                  aria-controls="productionstream-{{ productionstream.id }}-work-log">Work Log</button>
-        </h2>
-        <div id="productionstream-{{ productionstream.id }}-work-log"
-             class="accordion-collapse collapse {% if accordion_default_open == 'work-log' %}show{% endif %}"
-             aria-labelledby="productionstream-{{ productionstream.id }}-work-log-header"
-             data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
-          <div id="productionstream-{{ productionstream.id }}-work-log-body"
-               class="accordion-body"
-               hx-get="{% url 'production:_hx_productionstream_actions_work_log' productionstream_id=productionstream.id %}"
-               hx-trigger="intersect once, htmx:trigger from:this target:a.work_log_delete_btn delay:1000">
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-work-log-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'work-log' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-work-log"
+                      aria-expanded="false"
+                      aria-controls="productionstream-{{ productionstream.id }}-work-log">Work Log</button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-work-log"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'work-log' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-work-log-header"
+                 data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
+              <div id="productionstream-{{ productionstream.id }}-work-log-body"
+                   class="accordion-body"
+                   hx-get="{% url 'production:_hx_productionstream_actions_work_log' productionstream_id=productionstream.id %}"
+                   hx-trigger="intersect once, htmx:trigger from:this target:a.work_log_delete_btn delay:1000">
  
-            {% comment %} Placeholder before HTMX content loads {% endcomment %}
-            <div class="placeholder-glow">
-              <div class="row">
-                <div class="col-3">
-                  <div class="w-100 placeholder"></div>
-                </div>
-              </div>
-
-              {% for i_repeat in '12' %}
-                <div class="row">
-                  <div class="col-9">
-                    <div class="g-2">
-                      <div class="col-6 placeholder"></div>
-                      <div class="col-8 placeholder"></div>
+                {% comment %} Placeholder before HTMX content loads {% endcomment %}
+                <div class="placeholder-glow">
+                  <div class="row">
+                    <div class="col-3">
+                      <div class="w-100 placeholder"></div>
                     </div>
                   </div>
-                  <div class="col-3">
-                    <div class="g-2">
-                      <div class="col-12 placeholder"></div>
-                      <div class="offset-4 col-8 placeholder"></div>
+
+                  {% for i_repeat in '12' %}
+                    <div class="row">
+                      <div class="col-9">
+                        <div class="g-2">
+                          <div class="col-6 placeholder"></div>
+                          <div class="col-8 placeholder"></div>
+                        </div>
+                      </div>
+                      <div class="col-3">
+                        <div class="g-2">
+                          <div class="col-12 placeholder"></div>
+                          <div class="offset-4 col-8 placeholder"></div>
+                        </div>
+                      </div>
                     </div>
-                  </div>
-                </div>
-              {% endfor %}
+                  {% endfor %}
 
-              <div class="w-75 py-1 mb-3 placeholder"></div>
+                  <div class="w-75 py-1 mb-3 placeholder"></div>
 
-              {% for i_repeat in '123' %}
-                <div class="row">
-                  <div class="col-2">
-                    <div class="w-100 py-1 placeholder"></div>
-                  </div>
-                  <div class="col-10">
-                    <div class="w-100 pb-4 placeholder"></div>
-                  </div>
+                  {% for i_repeat in '123' %}
+                    <div class="row">
+                      <div class="col-2">
+                        <div class="w-100 py-1 placeholder"></div>
+                      </div>
+                      <div class="col-10">
+                        <div class="w-100 pb-4 placeholder"></div>
+                      </div>
+                    </div>
+                  {% endfor %}
                 </div>
-              {% endfor %}
-            </div>
-            {% comment %} End placeholder {% endcomment %}
+                {% comment %} End placeholder {% endcomment %}
 
+              </div>
+            </div>
           </div>
-        </div>
+        {% endif %}
+ 
       </div>
 
-    </div>
 
-    {% if perms.scipost.can_draft_publication or perms.scipost.can_publish_accepted_submission %}
-      <div class="mb-2 mb-md-0 mt-md-auto px-2">
+      {% if perms.scipost.can_draft_publication or perms.scipost.can_publish_accepted_submission %}
+        <div class="mb-2 mb-md-0 mt-md-auto px-2">
  
-        <div class="row mb-0 g-2">
-          {% if perms.scipost.can_publish_accepted_submission %}
-            <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 %}"
-                        {% if stream.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
-                </button>
+          <div class="row mb-0 g-2">
+            {% if perms.scipost.can_publish_accepted_submission %}
+              <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 %}"
+                          {% if stream.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
+                  </button>
+                </div>
               </div>
-            </div>
-          {% endif %}
+            {% endif %}
  
-          {% if perms.scipost.can_draft_publication and stream.status == 'accepted' %}
-            <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">
-                <a class="btn btn-primary text-white"
-                   href="{% url 'journals:create_publication' productionstream.submission.preprint.identifier_w_vn_nr %}">
-                  Draft publication
-                </a>
+            {% if perms.scipost.can_draft_publication and stream.status == 'accepted' %}
+              <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">
+                  <a class="btn btn-primary text-white"
+                     href="{% url 'journals:create_publication' productionstream.submission.preprint.identifier_w_vn_nr %}">
+                    Draft publication
+                  </a>
+                </div>
               </div>
-            </div>
-          {% endif %}
-        </div>
+            {% endif %}
+          </div>
  
-      </div>
+        </div>
+      {% endif %}
     {% endif %}
-
   </div>
 
   <div id="productionstream-{{ productionstream.id }}-event-container"
-       class="col-12 col-md-6 d-flex flex-column px-3">
+       class="col-12 col-md 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"
@@ -251,7 +250,7 @@
       {% comment %} End placeholder {% endcomment %}
 
     </div>
-
+ 
     <div id="productionstream-{{ productionstream.id }}-event-new-comment-form">
       <button hx-get="{% url 'production:_hx_event_form' productionstream_id=productionstream.id %}"
               hx-target="#productionstream-{{ productionstream.id }}-event-new-comment-form"
@@ -260,5 +259,4 @@
               class="btn btn-primary">Add a comment to this stream</button>
     </div>
   </div>
- 
 </div>
diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py
index ce83c7f03..23963b08e 100644
--- a/scipost_django/production/urls.py
+++ b/scipost_django/production/urls.py
@@ -140,16 +140,31 @@ urlpatterns = [
                                             production_views.decision,
                                             name="decision",
                                         ),
+                                        re_path(
+                                            "_hx_proofs_decision/(?P<decision>accept|decline)$",
+                                            production_views._hx_proofs_decision,
+                                            name="_hx_proofs_decision",
+                                        ),
                                         path(
                                             "send_to_authors",
                                             production_views.send_proofs,
                                             name="send_proofs",
                                         ),
+                                        path(
+                                            "_hx_send_to_authors",
+                                            production_views._hx_send_proofs,
+                                            name="_hx_send_proofs",
+                                        ),
                                         path(
                                             "toggle_access",
                                             production_views.toggle_accessibility,
                                             name="toggle_accessibility",
                                         ),
+                                        path(
+                                            "_hx_toggle_access",
+                                            production_views._hx_toggle_accessibility,
+                                            name="_hx_toggle_accessibility",
+                                        ),
                                     ]
                                 ),
                             ),
diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py
index 99777f932..e32e322b9 100644
--- a/scipost_django/production/views.py
+++ b/scipost_django/production/views.py
@@ -44,7 +44,7 @@ from .forms import (
     ProofsDecisionForm,
     AssignInvitationsOfficerForm,
 )
-from .permissions import is_production_user
+from .permissions import is_production_user, permission_required_htmx
 from .utils import proofs_slug_to_id, ProductionUtils
 
 
@@ -93,6 +93,10 @@ def _hx_productionstream_details_contents(request, productionstream_id):
 
     # Determine which accordion tab to open by default
     accordion_default_open = ""
+
+    if request.user.has_perm("scipost.can_assign_production_supervisor"):
+        accordion_default_open = "change-properties"
+
     if request.user.production_user == productionstream.supervisor:
         if productionstream.status in [
             constants.PROOFS_ACCEPTED,
@@ -408,7 +412,10 @@ def add_work_log(request, stream_id):
 
 
 @is_production_user()
-@permission_required("scipost.can_view_production", raise_exception=True)
+@permission_required_htmx(
+    ("scipost.can_view_production",),
+    message="You cannot view production.",
+)
 def _hx_productionstream_actions_work_log(request, productionstream_id):
     productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
     checker = ObjectPermissionChecker(request.user)
@@ -441,8 +448,13 @@ def _hx_productionstream_actions_work_log(request, productionstream_id):
 
 
 @is_production_user()
-@permission_required(
-    "scipost.can_take_decisions_related_to_proofs", raise_exception=True
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+    ),
+    message="You do not have permission to update the status of this stream.",
+    css_class="row",
 )
 def update_status(request, stream_id):
     productionstream = get_object_or_404(
@@ -543,7 +555,14 @@ def remove_officer(request, stream_id, officer_id):
 
 
 @is_production_user()
-@permission_required("scipost.can_assign_production_officer", raise_exception=True)
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_assign_production_officer",
+    ),
+    message="You do not have permission to update the officer of this stream.",
+    css_class="row",
+)
 @transaction.atomic
 def update_officer(request, stream_id):
     productionstream = get_object_or_404(
@@ -706,7 +725,14 @@ def remove_invitations_officer(request, stream_id, officer_id):
 
 
 @is_production_user()
-@permission_required("scipost.can_assign_production_officer", raise_exception=True)
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_assign_production_officer",
+    ),
+    message="You do not have permission to update the invitations officer of this stream.",
+    css_class="row",
+)
 @transaction.atomic
 def update_invitations_officer(request, stream_id):
     productionstream = get_object_or_404(
@@ -846,7 +872,11 @@ class UpdateEventView(UpdateView):
 
 
 @is_production_user()
-@permission_required("scipost.can_assign_production_supervisor", raise_exception=True)
+@permission_required_htmx(
+    ("scipost.can_view_production", "scipost.can_assign_production_supervisor"),
+    message="You do not have permission to update the supervisor of this stream.",
+    css_class="row",
+)
 @transaction.atomic
 def update_supervisor(request, stream_id):
     productionstream = get_object_or_404(
@@ -964,7 +994,13 @@ def mark_as_completed(request, stream_id):
 
 
 @is_production_user()
-@permission_required("scipost.can_upload_proofs", raise_exception=True)
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_upload_proofs",
+    ),
+    message="You cannot upload proofs for this stream.",
+)
 @transaction.atomic
 def upload_proofs(request, stream_id):
     """
@@ -1028,6 +1064,7 @@ def proofs(request, stream_id, version):
     return render(request, "production/proofs.html", context)
 
 
+@permission_required("scipost.can_view_production", raise_exception=True)
 def proofs_pdf(request, slug):
     """Open Proofs pdf."""
     if not request.user.is_authenticated:
@@ -1038,14 +1075,12 @@ def proofs_pdf(request, slug):
     proofs = get_object_or_404(Proofs, id=proofs_slug_to_id(slug))
     stream = proofs.stream
 
-    # Check if user has access!
+    # 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 proofs.stream.submission.authors.all()
-    if not access:
+    can_work_for_stream = checker.has_perm("can_work_for_stream", stream)
+    is_submission_author = request.user.contributor in stream.submission.authors.all()
+
+    if not (can_work_for_stream or is_submission_author):
         raise Http404
 
     # Passed the test! The user may see the file!
@@ -1130,6 +1165,37 @@ def toggle_accessibility(request, stream_id, version):
     except Proofs.DoesNotExist:
         raise Http404
 
+    proofs.accessible_for_authors = not proofs.accessible_for_authors
+    proofs.save()
+    messages.success(request, "Proofs accessibility updated.")
+    return redirect(reverse("production:proofs", args=(stream.id, proofs.version)))
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot make proofs accessible to the authors.",
+)
+def _hx_toggle_accessibility(request, stream_id, version):
+    """
+    Open/close accessibility of proofs to the authors.
+    """
+    stream = get_object_or_404(ProductionStream.objects.all(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_work_for_stream", stream):
+        return HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.exclude(status=constants.PROOFS_UPLOADED).get(
+            version=version
+        )
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
     proofs.accessible_for_authors = not proofs.accessible_for_authors
     proofs.save()
     messages.success(request, "Proofs accessibility updated.")
@@ -1184,6 +1250,56 @@ def decision(request, stream_id, version, decision):
     )
     prodevent.save()
     messages.success(request, "Proofs have been {decision}.".format(decision=decision))
+    return redirect(reverse("production:proofs", args=(stream.id, proofs.version)))
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot accept or decline proofs.",
+)
+@transaction.atomic
+def _hx_proofs_decision(request, stream_id, version, decision):
+    """
+    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)
+    if not checker.has_perm("can_work_for_stream", stream):
+        return HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.get(version=version, status=constants.PROOFS_UPLOADED)
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
+    if decision == "accept":
+        stream.status = constants.PROOFS_CHECKED
+        proofs.status = constants.PROOFS_ACCEPTED_SUP
+        decision = "accepted"
+    else:
+        stream.status = constants.PROOFS_TASKED
+        proofs.status = constants.PROOFS_DECLINED_SUP
+        proofs.accessible_for_authors = False
+        decision = "declined"
+
+    stream.save()
+    proofs.save()
+
+    prodevent = ProductionEvent(
+        stream=stream,
+        event="status",
+        comments="Proofs version {version} are {decision}.".format(
+            version=proofs.version, decision=decision
+        ),
+        noted_by=request.user.production_user,
+    )
+    prodevent.save()
+    messages.success(request, f"Proofs have been {decision}.")
     return render(
         request,
         "production/_hx_productionstream_actions_proofs_item.html",
@@ -1249,6 +1365,80 @@ def send_proofs(request, stream_id, version):
     return redirect(stream.get_absolute_url())
 
 
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot send proofs to the authors.",
+)
+@transaction.atomic
+def _hx_send_proofs(request, stream_id, version):
+    """
+    Send/open proofs to the authors.
+    """
+    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 HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.can_be_send().get(version=version)
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
+    proofs.status = constants.PROOFS_SENT
+    proofs.accessible_for_authors = True
+
+    if stream.status not in [
+        constants.PROOFS_PUBLISHED,
+        constants.PROOFS_CITED,
+        constants.PRODUCTION_STREAM_COMPLETED,
+    ]:
+        stream.status = constants.PROOFS_SENT
+        stream.save()
+
+    mail_request = MailEditorSubviewHTMX(
+        request,
+        "production_send_proofs",
+        proofs=proofs,
+        context={
+            "view_url": reverse("production:_hx_send_proofs", args=[stream_id, version])
+        },
+    )
+
+    print(request)
+
+    if request.method == "GET":
+        return mail_request.interrupt()
+
+    if mail_request.is_valid():
+        proofs.save()
+        stream.save()
+
+        mail_request.send_mail()
+
+        prodevent = ProductionEvent(
+            stream=stream,
+            event="status",
+            comments="Proofs version {version} sent to authors.".format(
+                version=proofs.version
+            ),
+            noted_by=request.user.production_user,
+        )
+        prodevent.save()
+
+        messages.success(request, "Proofs have been sent.")
+        return HTMXResponse("Proofs have been sent to the authors.", tag="success")
+    else:
+        messages.error(request, "Proofs have not been sent.")
+        return HTMXResponse(
+            "Proofs have not been sent. Please check the form.", tag="danger"
+        )
+
+
 def render_action_buttons(request, stream_id, key):
     productionstream = get_object_or_404(ProductionStream, pk=stream_id)
 
diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py
index 40b977a14..923d154c9 100644
--- a/scipost_django/scipost/views.py
+++ b/scipost_django/scipost/views.py
@@ -185,14 +185,16 @@ def _hx_messages(request):
 
 
 class HTMXResponse(HttpResponse):
-    tag = None
-    message = None
+    tag = "primary"
+    message = ""
+    css_class = ""
 
     def __init__(self, *args, **kwargs):
         tag = kwargs.pop("tag", self.tag)
         message = args[0] if args else kwargs.pop("message", self.message)
+        css_class = kwargs.pop("css_class", self.css_class)
 
-        alert_html = f"""<div class="text-{tag} border border-{tag} p-3">
+        alert_html = f"""<div class="text-{tag} border border-{tag} p-3 {css_class}">
                 {message}
             </div>"""
 
-- 
GitLab