Browse Source

Require a reason when tagging comment as "malice"

merge-requests/40/head
Deimos 6 years ago
parent
commit
735a6e5556
  1. 24
      tildes/alembic/versions/1ade2bf86efc_comment_tags_add_reason_column.py
  2. 15
      tildes/static/js/behaviors/comment-tag-button.js
  3. 10
      tildes/tildes/enums.py
  4. 13
      tildes/tildes/models/comment/comment_tag.py
  5. 3
      tildes/tildes/schemas/comment.py
  6. 10
      tildes/tildes/templates/macros/comments.jinja2
  7. 5
      tildes/tildes/views/api/web/comment.py

24
tildes/alembic/versions/1ade2bf86efc_comment_tags_add_reason_column.py

@ -0,0 +1,24 @@
"""comment_tags: add reason column
Revision ID: 1ade2bf86efc
Revises: 1996feae620d
Create Date: 2018-09-18 20:44:19.357105
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "1ade2bf86efc"
down_revision = "1996feae620d"
branch_labels = None
depends_on = None
def upgrade():
op.add_column("comment_tags", sa.Column("reason", sa.Text(), nullable=True))
def downgrade():
op.drop_column("comment_tags", "reason")

15
tildes/static/js/behaviors/comment-tag-button.js

@ -31,14 +31,29 @@ $.onmount('[data-js-comment-tag-button]', function() {
tagOptionActive = true; tagOptionActive = true;
} }
var tagPrompt = tag.getAttribute("data-js-reason-prompt");
if (tagOptionActive) { if (tagOptionActive) {
tag.className += " btn btn-used"; tag.className += " btn btn-used";
tag.setAttribute('data-ic-delete-from', tagURL + tagName); tag.setAttribute('data-ic-delete-from', tagURL + tagName);
// if the tag requires a prompt, confirm that they want to remove it
// (since we don't want to accidentally lose the reason they typed in)
if (tagPrompt) {
tag.setAttribute("data-ic-confirm", "Remove your "+tagName+" tag?");
}
$(tag).on('after.success.ic', function(evt) { $(tag).on('after.success.ic', function(evt) {
Tildes.removeUserTag(commentID, evt.target.textContent); Tildes.removeUserTag(commentID, evt.target.textContent);
}); });
} else { } else {
tag.setAttribute('data-ic-put-to', tagURL + tagName); tag.setAttribute('data-ic-put-to', tagURL + tagName);
if (tagPrompt) {
tag.setAttribute("data-ic-prompt", tagPrompt);
tag.setAttribute("data-ic-prompt-name", "reason");
}
$(tag).on('after.success.ic', function(evt) { $(tag).on('after.success.ic', function(evt) {
Tildes.addUserTag(commentID, evt.target.textContent); Tildes.addUserTag(commentID, evt.target.textContent);
}); });

10
tildes/tildes/enums.py

@ -3,6 +3,8 @@
"""Contains Enum classes.""" """Contains Enum classes."""
from typing import Optional
import enum import enum
@ -43,6 +45,14 @@ class CommentTagOption(enum.Enum):
OFFTOPIC = enum.auto() OFFTOPIC = enum.auto()
MALICE = enum.auto() MALICE = enum.auto()
@property
def reason_prompt(self) -> Optional[str]:
"""Return the reason prompt for this tag, if any."""
if self.name == "MALICE":
return "Why is this malicious? (required, will only be visible to admins)"
return None
class LogEventType(enum.Enum): class LogEventType(enum.Enum):
"""Enum for the types of events stored in logs.""" """Enum for the types of events stored in logs."""

13
tildes/tildes/models/comment/comment_tag.py

@ -4,8 +4,9 @@
"""Contains the CommentTag class.""" """Contains the CommentTag class."""
from datetime import datetime from datetime import datetime
from typing import Optional
from sqlalchemy import Column, ForeignKey, Integer, REAL, TIMESTAMP
from sqlalchemy import Column, ForeignKey, Integer, REAL, Text, TIMESTAMP
from sqlalchemy.dialects.postgresql import ENUM from sqlalchemy.dialects.postgresql import ENUM
from sqlalchemy.orm import backref, relationship from sqlalchemy.orm import backref, relationship
from sqlalchemy.sql.expression import text from sqlalchemy.sql.expression import text
@ -34,18 +35,26 @@ class CommentTag(DatabaseModel):
TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()") TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()")
) )
weight: float = Column(REAL, nullable=False, server_default=text("1.0")) weight: float = Column(REAL, nullable=False, server_default=text("1.0"))
reason: Optional[str] = Column(Text)
comment: Comment = relationship(Comment, backref=backref("tags", lazy=False)) comment: Comment = relationship(Comment, backref=backref("tags", lazy=False))
user: User = relationship(User, lazy=False, innerjoin=True) user: User = relationship(User, lazy=False, innerjoin=True)
def __init__( def __init__(
self, comment: Comment, user: User, tag: CommentTagOption, weight: float
self,
comment: Comment,
user: User,
tag: CommentTagOption,
weight: float,
reason: Optional[str] = None,
) -> None: ) -> None:
"""Add a new tag to a comment.""" """Add a new tag to a comment."""
# pylint: disable=too-many-arguments
self.comment_id = comment.comment_id self.comment_id = comment.comment_id
self.user_id = user.user_id self.user_id = user.user_id
self.tag = tag self.tag = tag
self.weight = weight self.weight = weight
self.reason = reason
@property @property
def name(self) -> str: def name(self) -> str:

3
tildes/tildes/schemas/comment.py

@ -6,7 +6,7 @@
from marshmallow import Schema from marshmallow import Schema
from tildes.enums import CommentTagOption from tildes.enums import CommentTagOption
from tildes.schemas.fields import Enum, ID36, Markdown
from tildes.schemas.fields import Enum, ID36, Markdown, SimpleString
class CommentSchema(Schema): class CommentSchema(Schema):
@ -25,6 +25,7 @@ class CommentTagSchema(Schema):
"""Marshmallow schema for comment tags.""" """Marshmallow schema for comment tags."""
name = Enum(CommentTagOption) name = Enum(CommentTagOption)
reason = SimpleString(missing=None)
class Meta: class Meta:
"""Always use strict checking so error handlers are invoked.""" """Always use strict checking so error handlers are invoked."""

10
tildes/tildes/templates/macros/comments.jinja2

@ -104,7 +104,7 @@
<span class="comment-tag-count"> <span class="comment-tag-count">
{{ weight }}: {{ weight }}:
{% for tag in comment.tags if tag.name == tag_name %} {% for tag in comment.tags if tag.name == tag_name %}
{{ username_linked(tag.user.username) }} ({{ tag.weight }})
{{ username_linked(tag.user.username) }} ({{ tag.weight }}{{ ' "%s"' % tag.reason if tag.reason else '' }})
{% endfor %} {% endfor %}
</span> </span>
</li> </li>
@ -237,7 +237,13 @@
<template id="comment-tag-options"> <template id="comment-tag-options">
<menu class="comment-tag-buttons"> <menu class="comment-tag-buttons">
{% for tag in options %} {% for tag in options %}
<li><a class="btn-comment-tag btn-comment-tag-{{ tag.name|lower }}">{{ tag.name|lower }}</a></li>
<li>
<a class="btn-comment-tag btn-comment-tag-{{ tag.name|lower }}"
{% if tag.reason_prompt %}
data-js-reason-prompt="{{ tag.reason_prompt }}"
{% endif %}
>{{ tag.name|lower }}</a>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</template> </template>

5
tildes/tildes/views/api/web/comment.py

@ -253,7 +253,8 @@ def delete_vote_comment(request: Request) -> dict:
renderer="comment_contents.jinja2", renderer="comment_contents.jinja2",
) )
@use_kwargs(CommentTagSchema(only=("name",)), locations=("matchdict",)) @use_kwargs(CommentTagSchema(only=("name",)), locations=("matchdict",))
def put_tag_comment(request: Request, name: CommentTagOption) -> Response:
@use_kwargs(CommentTagSchema(only=("reason",)))
def put_tag_comment(request: Request, name: CommentTagOption, reason: str) -> Response:
"""Add a tag to a comment.""" """Add a tag to a comment."""
comment = request.context comment = request.context
@ -263,7 +264,7 @@ def put_tag_comment(request: Request, name: CommentTagOption) -> Response:
if weight is None: if weight is None:
weight = request.registry.settings["tildes.default_user_comment_tag_weight"] weight = request.registry.settings["tildes.default_user_comment_tag_weight"]
tag = CommentTag(comment, request.user, name, weight)
tag = CommentTag(comment, request.user, name, weight, reason)
request.db_session.add(tag) request.db_session.add(tag)
try: try:

Loading…
Cancel
Save