diff --git a/app/blueprints/api/endpoints.py b/app/blueprints/api/endpoints.py index e9c13ae6..dd21d5e6 100644 --- a/app/blueprints/api/endpoints.py +++ b/app/blueprints/api/endpoints.py @@ -428,7 +428,10 @@ def list_all_reviews(): query = query.filter(PackageReview.author.has(User.username == request.args.get("author"))) if request.args.get("is_positive"): - query = query.filter(PackageReview.recommends == isYes(request.args.get("is_positive"))) + if isYes(request.args.get("is_positive")): + query = query.filter(PackageReview.rating >= 3) + else: + query = query.filter(PackageReview.rating < 3) q = request.args.get("q") if q: diff --git a/app/blueprints/donate/__init__.py b/app/blueprints/donate/__init__.py index 39b806a8..549be043 100644 --- a/app/blueprints/donate/__init__.py +++ b/app/blueprints/donate/__init__.py @@ -2,7 +2,7 @@ from flask import Blueprint, render_template from flask_login import current_user from sqlalchemy import or_ -from app.models import User, Package, PackageState, db, License +from app.models import User, Package, PackageState, db, License, PackageReview bp = Blueprint("donate", __name__) @@ -13,7 +13,7 @@ def donate(): if current_user.is_authenticated: reviewed_packages = Package.query.filter( Package.state == PackageState.APPROVED, - Package.reviews.any(author_id=current_user.id, recommends=True), + Package.reviews.any(PackageReview.author_id == current_user.id, PackageReview.rating >= 3), or_(Package.donate_url.isnot(None), Package.author.has(User.donate_url.isnot(None))) ).order_by(db.asc(Package.title)).all() diff --git a/app/blueprints/homepage/__init__.py b/app/blueprints/homepage/__init__.py index cb5e66d9..cf135dd9 100644 --- a/app/blueprints/homepage/__init__.py +++ b/app/blueprints/homepage/__init__.py @@ -47,7 +47,8 @@ def home(): .limit(20)).all() updated = updated[:4] - reviews = review_load(PackageReview.query.filter_by(recommends=True).order_by(db.desc(PackageReview.created_at))).limit(5).all() + reviews = review_load(PackageReview.query.filter(PackageReview.rating >= 3) + .order_by(db.desc(PackageReview.created_at))).limit(5).all() downloads_result = db.session.query(func.sum(Package.downloads)).one_or_none() downloads = 0 if not downloads_result or not downloads_result[0] else downloads_result[0] diff --git a/app/blueprints/packages/reviews.py b/app/blueprints/packages/reviews.py index b4c0dd6b..d0dfc197 100644 --- a/app/blueprints/packages/reviews.py +++ b/app/blueprints/packages/reviews.py @@ -43,10 +43,11 @@ def list_reviews(): class ReviewForm(FlaskForm): title = StringField(lazy_gettext("Title"), [InputRequired(), Length(3,100)]) comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)]) - recommends = RadioField(lazy_gettext("Private"), [InputRequired()], - choices=[("yes", lazy_gettext("Yes")), ("no", lazy_gettext("No"))]) + rating = RadioField(lazy_gettext("Rating"), [InputRequired()], + choices=[("5", lazy_gettext("Yes")), ("3", lazy_gettext("Neutral")), ("1", lazy_gettext("No"))]) submit = SubmitField(lazy_gettext("Save")) + @bp.route("/packages///review/", methods=["GET", "POST"]) @login_required @is_package_page @@ -69,7 +70,7 @@ def review(package): # Set default values if request.method == "GET" and review: form.title.data = review.thread.title - form.recommends.data = "yes" if review.recommends else "no" + form.rating.data = str(review.rating) form.comment.data = review.thread.first_reply.comment # Validate and submit @@ -85,7 +86,7 @@ def review(package): review.author = current_user db.session.add(review) - review.recommends = form.recommends.data == "yes" + review.rating = int(form.rating.data) thread = review.thread if not thread: @@ -227,7 +228,7 @@ def review_vote(package, review_id): def review_votes(package): user_biases = {} for review in package.reviews: - review_sign = 1 if review.recommends else -1 + review_sign = review.asWeight() for vote in review.votes: user_biases[vote.user.username] = user_biases.get(vote.user.username, [0, 0]) vote_sign = 1 if vote.is_positive else -1 diff --git a/app/flatpages/help/api.md b/app/flatpages/help/api.md index 666e4551..238b2db4 100644 --- a/app/flatpages/help/api.md +++ b/app/flatpages/help/api.md @@ -304,6 +304,7 @@ curl -X POST https://content.minetest.net/api/packages/username/name/screenshots * `user`: dictionary with `display_name` and `username`. * `title`: review title * `comment`: the text + * `rating`: 1 for negative, 3 for neutral, 5 for positive * `is_positive`: boolean * `created_at`: iso timestamp * `votes`: dictionary with `helpful` and `unhelpful`, @@ -316,6 +317,7 @@ curl -X POST https://content.minetest.net/api/packages/username/name/screenshots * `page`: page number, integer from 1 to max * `n`: number of results per page, max 100 * `author`: filter by review author username + * `rating`: 1 for negative, 3 for neutral, 5 for positive * `is_positive`: true or false. Default: null * `q`: filter by title (case insensitive, no fulltext search) diff --git a/app/models/packages.py b/app/models/packages.py index 56194782..078ca82e 100644 --- a/app/models/packages.py +++ b/app/models/packages.py @@ -779,7 +779,7 @@ class Package(db.Model): } def recalcScore(self): - review_scores = [ 100 * r.asSign() for r in self.reviews ] + review_scores = [ 100 * r.asWeight() for r in self.reviews ] self.score = self.score_downloads + sum(review_scores) diff --git a/app/models/threads.py b/app/models/threads.py index 2ca1d7b4..22bec4bb 100644 --- a/app/models/threads.py +++ b/app/models/threads.py @@ -177,7 +177,7 @@ class PackageReview(db.Model): author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) author = db.relationship("User", foreign_keys=[author_id], back_populates="reviews") - recommends = db.Column(db.Boolean, nullable=False) + rating = db.Column(db.Integer, nullable=False) thread = db.relationship("Thread", uselist=False, back_populates="review") votes = db.relationship("PackageReviewVote", back_populates="review", cascade="all, delete, delete-orphan") @@ -194,7 +194,8 @@ class PackageReview(db.Model): def getAsDictionary(self, include_package=False): pos, neg, _user = self.get_totals() ret = { - "is_positive": self.recommends, + "is_positive": self.rating >= 3, + "rating": self.rating, "user": { "username": self.author.username, "display_name": self.author.display_name, @@ -211,8 +212,8 @@ class PackageReview(db.Model): ret["package"] = self.package.getAsDictionaryKey() return ret - def asSign(self): - return 1 if self.recommends else -1 + def asWeight(self): + return 2.0 * self.rating / 5.0 - 1 def getEditURL(self): return self.package.getURL("packages.review") diff --git a/app/templates/index.html b/app/templates/index.html index 29549f41..239608f6 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -69,9 +69,11 @@ - +{{ package.reviews | selectattr("recommends") | list | length }} + +{{ package.reviews | selectattr("rating", "equalto", 5) | list | length }} / - -{{ package.reviews | rejectattr("recommends") | list | length }} + {{ package.reviews | selectattr("rating", "equalto", 3) | list | length }} + / + -{{ package.reviews | selectattr("rating", "equalto", 1) | list | length }} diff --git a/app/templates/macros/reviews.html b/app/templates/macros/reviews.html index 972b30d9..0c95c80b 100644 --- a/app/templates/macros/reviews.html +++ b/app/templates/macros/reviews.html @@ -31,10 +31,12 @@
- {% if review.recommends %} + {% if review.rating > 3 %} - {% else %} + {% elif review.rating < 3 %} + {% else %} + {% endif %}
{% if review.thread %} @@ -112,11 +114,15 @@
+
@@ -149,11 +155,15 @@

- - + diff --git a/app/templates/macros/threads.html b/app/templates/macros/threads.html index 710e7faa..45ed1051 100644 --- a/app/templates/macros/threads.html +++ b/app/templates/macros/threads.html @@ -224,10 +224,12 @@ {% elif not t.review %} - {% elif t.review.recommends %} + {% elif t.review.rating > 3 %} - {% else %} + {% elif t.review.rating < 3 %} + {% else %} + {% endif %} {{ t.title }} diff --git a/app/templates/packages/review_create_edit.html b/app/templates/packages/review_create_edit.html index c174dbcc..eccd26a8 100644 --- a/app/templates/packages/review_create_edit.html +++ b/app/templates/packages/review_create_edit.html @@ -34,7 +34,7 @@

{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}

- {{ render_toggle_field(form.recommends, icons={"yes":"fa-thumbs-up", "no":"fa-thumbs-down"}) }} + {{ render_toggle_field(form.rating, icons={"5":"fa-thumbs-up", "3": "fa-minus", "1":"fa-thumbs-down"}) }}

{{ _("Why or why not? Try to be constructive") }} diff --git a/app/templates/packages/review_votes.html b/app/templates/packages/review_votes.html index 13bc62e6..2e4aec8f 100644 --- a/app/templates/packages/review_votes.html +++ b/app/templates/packages/review_votes.html @@ -54,10 +54,12 @@ {% for review in reviews %} - {% if review.recommends %} + {% if review.rating > 3 %} - {% else %} + {% elif review.rating < 3 %} + {% else %} + {% endif %} {{ review.thread.title }} @@ -86,4 +88,4 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index 57959439..3067669f 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -198,9 +198,11 @@ - +{{ package.reviews | selectattr("recommends") | list | length }} + +{{ package.reviews | selectattr("rating", "equalto", 5) | list | length }} / - -{{ package.reviews | rejectattr("recommends") | list | length }} + {{ package.reviews | selectattr("rating", "equalto", 3) | list | length }} + / + -{{ package.reviews | selectattr("rating", "equalto", 1) | list | length }} {% if package.website %} diff --git a/app/templates/threads/view.html b/app/templates/threads/view.html index a94ba394..8bb7591e 100644 --- a/app/templates/threads/view.html +++ b/app/templates/threads/view.html @@ -3,10 +3,12 @@ {% block title %} {%- if thread.package -%} {%- if thread.review -%} - {%- if thread.review.recommends -%} + {%- if thread.review.rating > 3 -%} {%- set rating = "👍" -%} - {%- else -%} + {%- elif thread.review.rating < 3 -%} {%- set rating = "👎" -%} + {%- else -%} + {%- set rating = "-" -%} {%- endif -%} {%- endif -%} {{ rating }} {{ thread.title }} - {{ thread.package.title }} @@ -70,10 +72,12 @@

{% if thread.review %} - {% if thread.review.recommends %} + {% if thread.review.rating > 3 %} - {% else %} + {% elif thread.review.rating < 3 %} + {% else %} + {% endif %} {% endif %} {% if thread.private %}🔒 {% endif %}{{ thread.title }} diff --git a/migrations/versions/dabd7ab14339_.py b/migrations/versions/dabd7ab14339_.py new file mode 100644 index 00000000..e8df0bcf --- /dev/null +++ b/migrations/versions/dabd7ab14339_.py @@ -0,0 +1,37 @@ +"""empty message + +Revision ID: dabd7ab14339 +Revises: aa53b4d36c50 +Create Date: 2023-04-15 01:18:53.212673 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'dabd7ab14339' +down_revision = 'aa53b4d36c50' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('package_review', sa.Column('rating', sa.Integer(), nullable=True)) + op.execute(""" + UPDATE package_review SET rating = CASE + WHEN recommends THEN 5 + ELSE 1 + END; + """) + op.drop_column('package_review', 'recommends') + op.alter_column('package_review', 'rating', nullable=False) + + +def downgrade(): + op.add_column('package_review', sa.Column('recommends', sa.BOOLEAN(), autoincrement=False, nullable=True)) + op.execute(""" + UPDATE package_review SET recommends = rating >= 3; + """) + op.drop_column('package_review', 'rating') + op.alter_column('package_review', 'recommends', nullable=False)