- changed status to open
Avoid the onclick attribute in forms
In postman/view.html
and in postman/base_folder.html
, the action
attribute is empty and set when a button is clicked with the onclick
attribute. This method has two drawbacks:
- the resulting HTML is invalid (the
action
attribute cannot be left empty) - nothing happens with a strong content security policy and messages cannot be deleted, archived or marked as read or unread.
A valid method is to set a name
and a value
attributes to the buttons, like this:
`
{% block pm_delete_button %}<button name="action" value="delete" type="submit" class="btn btn-sm btn-danger pm_btn pm_btn-delete" >{% trans "Delete" %}</button>{% endblock %}
{% block pm_archive_button %}<button name="action" value="archive" type="submit" class="btn btn-sm btn-primary pm_btn pm_btn-archive" >{% trans "Archive" %}</button>{% endblock %}
{% block pm_undelete_button %}<button name="action" value="undelete" type="submit" class="btn btn-sm btn-primary pm_btn pm_btn-undelete" >{% trans "Undelete" %}</button>{% endblock %}
{% block pm_read_button %}<button name="action" value="read" type="submit" class="btn btn-sm btn-primary pm_btn pm_btn-read" >{% trans "Mark as read" %}</button>{% endblock %}
{% block pm_unread_button %}<button name="action" value="unread" type="submit" class="btn btn-sm btn-primary pm_btn pm_btn-unread" >{% trans "Mark as unread" %}</button>{% endblock %}`
and to use the same view for all actions:
class MessageUpdateView(UpdateDualMixin, View):
"""View for updating messages or conversations.
This single view replaces the following views: `ArchiveView`, `DeleteView`,
`UndeleteView`, 'MarkReadView', 'MarkUnreadView'.
This is required to avoid the onclick attribute in the template (forbidden by CSP).
"""
@property
def selected_action(self):
"""Get the action to perform on the selected messages or conversations."""
action = self.request.POST.get("action")
if action not in {"delete", "archive", "undelete", "read", "unread"}:
raise PermissionDenied
return action
def _action(self, user: AbstractBaseUser, filter_: Q):
"""Perform the action on the selected messages or conversations."""
if self.selected_action in {"read", "unread"}:
Message.objects.as_recipient(user, filter_).filter(
**{f"{self.field_bit}__isnull": bool(self.field_value)}
).update(**{self.field_bit: self.field_value})
else:
super()._action(user, filter_)
# an empty set cannot be estimated as an error, it may be just a badly chosen selection
@property
def field_bit(self):
"""Get the field to update."""
return {
"delete": "deleted_at",
"archive": "archived",
"undelete": "deleted_at",
"read": "read_at",
"unread": "read_at",
}[self.selected_action]
@property
def success_msg(self):
"""Get the success message to display."""
return {
"delete": _("Messages or conversations successfully deleted."),
"archive": _("Messages or conversations successfully archived."),
"undelete": _("Messages or conversations successfully recovered."),
"read": _("Messages or conversations successfully marked as read."),
"unread": _("Messages or conversations successfully marked as unread."),
}[self.selected_action]
@property
def field_value(self):
"""Get the value to set the field to."""
n = now()
return {"delete": n, "archive": True, "undelete": None, "read": n, "unread": None}[self.selected_action]
Comments (5)
-
repo owner -
repo owner Just saw that now. In html5, specifying
action=""
seems indeed invalid (but valid in html4). An alternative is to not specifyaction
and to specifyformaction
on the submit buttons.https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action
Are you knowing that and can you give it a trial?
-
reporter I did not know the existence of the formaction attribute. It seems to be a simple way to remove the JavaScript code.
-
repo owner So, now, changes should be only in the templates:
diff --git a/postman/templates/postman/base_folder.html b/postman/templates/postman/base_folder.html @@ -19,13 +19,13 @@ in the INSTALLED_APPS setting. <span class="pm_by-mode">{% if by_message %}<a href="{{ by_conversation_url }}">{% endif %}{% trans "by conversation" %}{% if by_message %}</a>{% endif %}</span> <span class="pm_by-mode">{% if by_conversation %}<a href="{{ by_message_url }}">{% endif %}{% trans "by message" %}{% if by_conversation %}</a>{% endif %}</span> </div>{% endblock pm_by_modes %} -<form action="{% block pm_form_action %}{% endblock %}" method="post">{% csrf_token %} +<form method="post">{% csrf_token %} {% block pm_form_buttons %}<span id="pm_buttons"> -{% block pm_delete_button %}<button type="submit" class="pm_btn pm_btn-delete" onclick="this.form.action='{% url 'postman:delete' %}'">{% trans "Delete" %}</button>{% endblock %} -{% block pm_archive_button %}<button type="submit" class="pm_btn pm_btn-archive" onclick="this.form.action='{% url 'postman:archive' %}'">{% trans "Archive" %}</button>{% endblock %} -{% block pm_undelete_button %}<button type="submit" class="pm_btn pm_btn-undelete" onclick="this.form.action='{% url 'postman:undelete' %}'">{% trans "Undelete" %}</button>{% endblock %} -{% block pm_read_button %}<button type="submit" class="pm_btn pm_btn-read" onclick="this.form.action='{% url 'postman:mark-read' %}'">{% trans "Mark as read" %}</button>{% endblock %} -{% block pm_unread_button %}<button type="submit" class="pm_btn pm_btn-unread" onclick="this.form.action='{% url 'postman:mark-unread' %}'">{% trans "Mark as unread" %}</button>{% endblock %} +{% block pm_delete_button %}<button type="submit" class="pm_btn pm_btn-delete" formaction="{% url 'postman:delete' %}">{% trans "Delete" %}</button>{% endblock %} +{% block pm_archive_button %}<button type="submit" class="pm_btn pm_btn-archive" formaction="{% url 'postman:archive' %}">{% trans "Archive" %}</button>{% endblock %} +{% block pm_undelete_button %}<button type="submit" class="pm_btn pm_btn-undelete" formaction="{% url 'postman:undelete' %}">{% trans "Undelete" %}</button>{% endblock %} +{% block pm_read_button %}<button type="submit" class="pm_btn pm_btn-read" formaction="{% url 'postman:mark-read' %}">{% trans "Mark as read" %}</button>{% endblock %} +{% block pm_unread_button %}<button type="submit" class="pm_btn pm_btn-unread" formaction="{% url 'postman:mark-unread' %}">{% trans "Mark as unread" %}</button>{% endblock %} </span>{% endblock %} <table id="pm_messages"> <thead>
diff --git a/postman/templates/postman/base_write.html b/postman/templates/postman/base_write.html @@ -10,7 +10,7 @@ {% block content %} <div id="postman"> <h1>{% block pm_write_title %}{% endblock %}</h1> -<form action="{% if next_url %}?next={{ next_url|urlencode }}{% endif %}" method="post">{% csrf_token %} +<form{% if next_url %} action="?next={{ next_url|urlencode }}"{% endif %} method="post">{% csrf_token %} <table> {% block pm_write_recipient %}{% endblock %} {{ form.as_table }}
diff --git a/postman/templates/postman/view.html b/postman/templates/postman/view.html @@ -15,14 +15,14 @@ <div class="pm_body">{{ message.body|linebreaksbr }}</div> </div> {% if forloop.last %} -<form action="" method="post">{% csrf_token %} +<form method="post">{% csrf_token %} <input type="hidden" {% if pm_messages|length > 1 and message.thread_id %}name="tpks" value="{{ message.thread_id }}"{% else %}name="pks" value="{{ message.pk }}"{% endif %}> <a href="{{ next_url }}" class="pm_action pm_action-back">{% trans "Back" %}</a> <span id="pm_buttons"> <button type="submit" class="pm_btn pm_btn-delete" - onclick="this.form.action='{% url 'postman:delete' %}?next={{ next_url|urlencode }}'">{% trans "Delete" %}</button> +formaction="{% url 'postman:delete' %}?next={{ next_url|urlencode }}">{% trans "Delete" %}</button> {% if not archived %}<button type="submit" class="pm_btn pm_btn-archive" - onclick="this.form.action='{% url 'postman:archive' %}?next={{ next_url|urlencode }}'">{% trans "Archive" %}</button>{% endif %} +formaction="{% url 'postman:archive' %}?next={{ next_url|urlencode }}">{% trans "Archive" %}</button>{% endif %} </span> {% if reply_to_pk %}<a href="{% url 'postman:reply' reply_to_pk %}?next={{ next_url|urlencode }}" class="pm_action pm_action-reply">{% trans "Reply" %}</a>{% endif %}
-
repo owner - changed status to resolved
Fixed in repository.
- Log in to comment
OK for the name/value attributes.
For backwards compatibility, the existing views will be kept and marked as deprecated.
The foreseen code is: