diff --git a/aurweb/routers/accounts.py b/aurweb/routers/accounts.py index 07962c37..77988d7f 100644 --- a/aurweb/routers/accounts.py +++ b/aurweb/routers/accounts.py @@ -209,6 +209,7 @@ def make_account_form_context( context["cn"] = args.get("CN", user.CommentNotify) context["un"] = args.get("UN", user.UpdateNotify) context["on"] = args.get("ON", user.OwnershipNotify) + context["hdc"] = args.get("HDC", user.HideDeletedComments) context["inactive"] = args.get("J", user.InactivityTS != 0) else: context["username"] = args.get("U", str()) @@ -227,6 +228,7 @@ def make_account_form_context( context["cn"] = args.get("CN", True) context["un"] = args.get("UN", False) context["on"] = args.get("ON", True) + context["hdc"] = args.get("HDC", False) context["inactive"] = args.get("J", False) context["password"] = args.get("P", str()) @@ -253,6 +255,7 @@ async def account_register( CN: bool = Form(default=False), # Comment Notify CU: bool = Form(default=False), # Update Notify CO: bool = Form(default=False), # Owner Notify + HDC: bool = Form(default=False), # Hide Deleted Comments captcha: str = Form(default=str()), ): context = await make_variable_context(request, "Register") @@ -281,6 +284,7 @@ async def account_register_post( CN: bool = Form(default=False), UN: bool = Form(default=False), ON: bool = Form(default=False), + HDC: bool = Form(default=False), captcha: str = Form(default=None), captcha_salt: str = Form(...), ): @@ -334,6 +338,7 @@ async def account_register_post( CommentNotify=CN, UpdateNotify=UN, OwnershipNotify=ON, + HideDeletedComments=HDC, ResetKey=resetkey, AccountType=atype, ) @@ -417,6 +422,7 @@ async def account_edit_post( CN: bool = Form(default=False), # Comment Notify UN: bool = Form(default=False), # Update Notify ON: bool = Form(default=False), # Owner Notify + HDC: bool = Form(default=False), # Hide Deleted Comments T: int = Form(default=None), passwd: str = Form(default=str()), ): diff --git a/aurweb/schema.py b/aurweb/schema.py index 0ba3e9c2..d01d07c9 100644 --- a/aurweb/schema.py +++ b/aurweb/schema.py @@ -108,6 +108,12 @@ Users = Table( Column("OwnershipNotify", TINYINT(1), nullable=False, server_default=text("1")), Column("SSOAccountID", String(255), nullable=True, unique=True), Index("UsersAccountTypeID", "AccountTypeID"), + Column( + "HideDeletedComments", + TINYINT(unsigned=True), + nullable=False, + server_default=text("0"), + ), mysql_engine="InnoDB", mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci", diff --git a/aurweb/users/update.py b/aurweb/users/update.py index df41f843..21349a39 100644 --- a/aurweb/users/update.py +++ b/aurweb/users/update.py @@ -22,6 +22,7 @@ def simple( CN: bool = False, UN: bool = False, ON: bool = False, + HDC: bool = False, S: bool = False, user: models.User = None, **kwargs, @@ -41,6 +42,7 @@ def simple( user.CommentNotify = strtobool(CN) user.UpdateNotify = strtobool(UN) user.OwnershipNotify = strtobool(ON) + user.HideDeletedComments = strtobool(HDC) @db.retry_deadlock diff --git a/migrations/versions/e4e49ffce091_add_hidedeletedcomments_to_user.py b/migrations/versions/e4e49ffce091_add_hidedeletedcomments_to_user.py new file mode 100644 index 00000000..bc18b519 --- /dev/null +++ b/migrations/versions/e4e49ffce091_add_hidedeletedcomments_to_user.py @@ -0,0 +1,33 @@ +"""Add HideDeletedComments to User + +Revision ID: e4e49ffce091 +Revises: 9e3158957fd7 +Create Date: 2023-04-19 23:24:25.854874 + +""" +from alembic import op +from sqlalchemy.exc import OperationalError + +from aurweb.models.user import User + +# revision identifiers, used by Alembic. +revision = "e4e49ffce091" +down_revision = "9e3158957fd7" +branch_labels = None +depends_on = None + +table = User.__table__ + + +def upgrade(): + try: + op.add_column(table.name, table.c.HideDeletedComments) + except OperationalError: + print( + f"Column HideDeletedComments already exists in '{table.name}'," + f" skipping migration." + ) + + +def downgrade(): + op.drop_column(table.name, "HideDeletedComments") diff --git a/po/aurweb.pot b/po/aurweb.pot index 7e798a21..f4e3c1ba 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -1402,6 +1402,10 @@ msgstr "" msgid "Specify multiple SSH Keys separated by new line, empty lines are ignored." msgstr "" +#: templates/partials/account_form.html +msgid "Hide deleted comments" +msgstr "" + #: template/account_edit_form.php msgid "SSH Public Key" msgstr "" diff --git a/templates/partials/account/comment.html b/templates/partials/account/comment.html index f88aab02..41c9ac08 100644 --- a/templates/partials/account/comment.html +++ b/templates/partials/account/comment.html @@ -4,7 +4,7 @@ {% endif %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %} - +{% if not (request.user.HideDeletedComments and comment.DelTS) %} {% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}

{{ @@ -38,3 +38,4 @@ {% include "partials/comment_content.html" %} {% endif %} +{% endif %} diff --git a/templates/partials/account_form.html b/templates/partials/account_form.html index a433a57d..4d135a56 100644 --- a/templates/partials/account_form.html +++ b/templates/partials/account_form.html @@ -182,7 +182,7 @@ maxlength="50" name="K" value="{{ pgp }}">

- +

- +

+ +

+ + + +

+ {% if form_type == "UpdateAccount" %} diff --git a/templates/partials/packages/comment.html b/templates/partials/packages/comment.html index e4818837..faac0753 100644 --- a/templates/partials/packages/comment.html +++ b/templates/partials/packages/comment.html @@ -6,6 +6,7 @@ {% endif %} {% if not comment.Deleter or request.user.has_credential(creds.COMMENT_VIEW_DELETED, approved=[comment.Deleter]) %} +{% if not (request.user.HideDeletedComments and comment.DelTS) %}

{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %} {% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %} @@ -41,3 +42,4 @@ {% include "partials/comment_content.html" %} {% endif %} +{% endif %} diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index d4b0babc..21ccdd7b 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -122,6 +122,22 @@ def tu_user(): yield tu_user +@pytest.fixture +def user_who_hates_grey_comments() -> User: + """Yield a specific User who doesn't like grey comments.""" + account_type = db.query(AccountType, AccountType.ID == USER_ID).first() + with db.begin(): + user_who_hates_grey_comments = db.create( + User, + Username="test_hater", + Email="test_hater@example.org", + Passwd="testPassword", + AccountType=account_type, + HideDeletedComments=True, + ) + yield user_who_hates_grey_comments + + @pytest.fixture def package(maintainer: User) -> Package: """Yield a Package created by user.""" @@ -193,6 +209,23 @@ def comment(user: User, package: Package) -> PackageComment: yield comment +@pytest.fixture +def deleted_comment(user: User, package: Package) -> PackageComment: + pkgbase = package.PackageBase + now = time.utcnow() + with db.begin(): + comment = db.create( + PackageComment, + User=user, + PackageBase=pkgbase, + Comments="Test comment.", + RenderedComment=str(), + CommentTS=now, + DelTS=now, + ) + yield comment + + @pytest.fixture def packages(maintainer: User) -> list[Package]: """Yield 55 packages named pkg_0 .. pkg_54.""" @@ -409,7 +442,9 @@ def test_paged_depends_required(client: TestClient, package: Package): assert "Show 6 more" not in resp.text -def test_package_comments(client: TestClient, user: User, package: Package): +def test_package_comments( + client: TestClient, user: User, user_who_hates_grey_comments: User, package: Package +): now = time.utcnow() with db.begin(): comment = db.create( @@ -419,6 +454,14 @@ def test_package_comments(client: TestClient, user: User, package: Package): Comments="Test comment", CommentTS=now, ) + deleted_comment = db.create( + PackageComment, + PackageBase=package.PackageBase, + User=user, + Comments="Deleted Test comment", + CommentTS=now, + DelTS=now - 1, + ) cookies = {"AURSID": user.login(Request(), "testPassword")} with client as request: @@ -426,12 +469,29 @@ def test_package_comments(client: TestClient, user: User, package: Package): resp = request.get(package_endpoint(package)) assert resp.status_code == int(HTTPStatus.OK) + root = parse_root(resp.text) + expected = [comment.Comments, deleted_comment.Comments] + comments = root.xpath( + './/div[contains(@class, "package-comments")]' + '/div[@class="article-content"]/div/text()' + ) + assert len(comments) == 2 + for i, row in enumerate(expected): + assert comments[i].strip() == row + + cookies = {"AURSID": user_who_hates_grey_comments.login(Request(), "testPassword")} + with client as request: + request.cookies = cookies + resp = request.get(package_endpoint(package)) + assert resp.status_code == int(HTTPStatus.OK) + root = parse_root(resp.text) expected = [comment.Comments] comments = root.xpath( './/div[contains(@class, "package-comments")]' '/div[@class="article-content"]/div/text()' ) + assert len(comments) == 1 # Deleted comment is hidden for i, row in enumerate(expected): assert comments[i].strip() == row