mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 14:02:24 +01:00
Add ability to make neutral reviews
This commit is contained in:
parent
1235bc14db
commit
b1bd39c0fc
@ -428,7 +428,10 @@ def list_all_reviews():
|
|||||||
query = query.filter(PackageReview.author.has(User.username == request.args.get("author")))
|
query = query.filter(PackageReview.author.has(User.username == request.args.get("author")))
|
||||||
|
|
||||||
if request.args.get("is_positive"):
|
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")
|
q = request.args.get("q")
|
||||||
if q:
|
if q:
|
||||||
|
@ -2,7 +2,7 @@ from flask import Blueprint, render_template
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from sqlalchemy import or_
|
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__)
|
bp = Blueprint("donate", __name__)
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ def donate():
|
|||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
reviewed_packages = Package.query.filter(
|
reviewed_packages = Package.query.filter(
|
||||||
Package.state == PackageState.APPROVED,
|
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)))
|
or_(Package.donate_url.isnot(None), Package.author.has(User.donate_url.isnot(None)))
|
||||||
).order_by(db.asc(Package.title)).all()
|
).order_by(db.asc(Package.title)).all()
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ def home():
|
|||||||
.limit(20)).all()
|
.limit(20)).all()
|
||||||
updated = updated[:4]
|
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_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]
|
downloads = 0 if not downloads_result or not downloads_result[0] else downloads_result[0]
|
||||||
|
@ -43,10 +43,11 @@ def list_reviews():
|
|||||||
class ReviewForm(FlaskForm):
|
class ReviewForm(FlaskForm):
|
||||||
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(3,100)])
|
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(3,100)])
|
||||||
comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)])
|
comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)])
|
||||||
recommends = RadioField(lazy_gettext("Private"), [InputRequired()],
|
rating = RadioField(lazy_gettext("Rating"), [InputRequired()],
|
||||||
choices=[("yes", lazy_gettext("Yes")), ("no", lazy_gettext("No"))])
|
choices=[("5", lazy_gettext("Yes")), ("3", lazy_gettext("Neutral")), ("1", lazy_gettext("No"))])
|
||||||
submit = SubmitField(lazy_gettext("Save"))
|
submit = SubmitField(lazy_gettext("Save"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/review/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/review/", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@is_package_page
|
@is_package_page
|
||||||
@ -69,7 +70,7 @@ def review(package):
|
|||||||
# Set default values
|
# Set default values
|
||||||
if request.method == "GET" and review:
|
if request.method == "GET" and review:
|
||||||
form.title.data = review.thread.title
|
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
|
form.comment.data = review.thread.first_reply.comment
|
||||||
|
|
||||||
# Validate and submit
|
# Validate and submit
|
||||||
@ -85,7 +86,7 @@ def review(package):
|
|||||||
review.author = current_user
|
review.author = current_user
|
||||||
db.session.add(review)
|
db.session.add(review)
|
||||||
|
|
||||||
review.recommends = form.recommends.data == "yes"
|
review.rating = int(form.rating.data)
|
||||||
|
|
||||||
thread = review.thread
|
thread = review.thread
|
||||||
if not thread:
|
if not thread:
|
||||||
@ -227,7 +228,7 @@ def review_vote(package, review_id):
|
|||||||
def review_votes(package):
|
def review_votes(package):
|
||||||
user_biases = {}
|
user_biases = {}
|
||||||
for review in package.reviews:
|
for review in package.reviews:
|
||||||
review_sign = 1 if review.recommends else -1
|
review_sign = review.asWeight()
|
||||||
for vote in review.votes:
|
for vote in review.votes:
|
||||||
user_biases[vote.user.username] = user_biases.get(vote.user.username, [0, 0])
|
user_biases[vote.user.username] = user_biases.get(vote.user.username, [0, 0])
|
||||||
vote_sign = 1 if vote.is_positive else -1
|
vote_sign = 1 if vote.is_positive else -1
|
||||||
|
@ -304,6 +304,7 @@ curl -X POST https://content.minetest.net/api/packages/username/name/screenshots
|
|||||||
* `user`: dictionary with `display_name` and `username`.
|
* `user`: dictionary with `display_name` and `username`.
|
||||||
* `title`: review title
|
* `title`: review title
|
||||||
* `comment`: the text
|
* `comment`: the text
|
||||||
|
* `rating`: 1 for negative, 3 for neutral, 5 for positive
|
||||||
* `is_positive`: boolean
|
* `is_positive`: boolean
|
||||||
* `created_at`: iso timestamp
|
* `created_at`: iso timestamp
|
||||||
* `votes`: dictionary with `helpful` and `unhelpful`,
|
* `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
|
* `page`: page number, integer from 1 to max
|
||||||
* `n`: number of results per page, max 100
|
* `n`: number of results per page, max 100
|
||||||
* `author`: filter by review author username
|
* `author`: filter by review author username
|
||||||
|
* `rating`: 1 for negative, 3 for neutral, 5 for positive
|
||||||
* `is_positive`: true or false. Default: null
|
* `is_positive`: true or false. Default: null
|
||||||
* `q`: filter by title (case insensitive, no fulltext search)
|
* `q`: filter by title (case insensitive, no fulltext search)
|
||||||
|
|
||||||
|
@ -779,7 +779,7 @@ class Package(db.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def recalcScore(self):
|
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)
|
self.score = self.score_downloads + sum(review_scores)
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ class PackageReview(db.Model):
|
|||||||
author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
author_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
||||||
author = db.relationship("User", foreign_keys=[author_id], back_populates="reviews")
|
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")
|
thread = db.relationship("Thread", uselist=False, back_populates="review")
|
||||||
votes = db.relationship("PackageReviewVote", back_populates="review", cascade="all, delete, delete-orphan")
|
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):
|
def getAsDictionary(self, include_package=False):
|
||||||
pos, neg, _user = self.get_totals()
|
pos, neg, _user = self.get_totals()
|
||||||
ret = {
|
ret = {
|
||||||
"is_positive": self.recommends,
|
"is_positive": self.rating >= 3,
|
||||||
|
"rating": self.rating,
|
||||||
"user": {
|
"user": {
|
||||||
"username": self.author.username,
|
"username": self.author.username,
|
||||||
"display_name": self.author.display_name,
|
"display_name": self.author.display_name,
|
||||||
@ -211,8 +212,8 @@ class PackageReview(db.Model):
|
|||||||
ret["package"] = self.package.getAsDictionaryKey()
|
ret["package"] = self.package.getAsDictionaryKey()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def asSign(self):
|
def asWeight(self):
|
||||||
return 1 if self.recommends else -1
|
return 2.0 * self.rating / 5.0 - 1
|
||||||
|
|
||||||
def getEditURL(self):
|
def getEditURL(self):
|
||||||
return self.package.getURL("packages.review")
|
return self.package.getURL("packages.review")
|
||||||
|
@ -69,9 +69,11 @@
|
|||||||
<span class="btn" title="{{ _('Reviews') }}">
|
<span class="btn" title="{{ _('Reviews') }}">
|
||||||
<i class="fas fa-star-half-alt"></i>
|
<i class="fas fa-star-half-alt"></i>
|
||||||
<span class="count">
|
<span class="count">
|
||||||
+{{ 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 }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,10 +31,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-auto pl-1 pr-3 pt-2 text-center" style=" font-size: 200%;">
|
<div class="col-md-auto pl-1 pr-3 pt-2 text-center" style=" font-size: 200%;">
|
||||||
{% if review.recommends %}
|
{% if review.rating > 3 %}
|
||||||
<i class="fas fa-thumbs-up" style="color:#6f6;"></i>
|
<i class="fas fa-thumbs-up" style="color:#6f6;"></i>
|
||||||
{% else %}
|
{% elif review.rating < 3 %}
|
||||||
<i class="fas fa-thumbs-down" style="color:#f66;"></i>
|
<i class="fas fa-thumbs-down" style="color:#f66;"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-minus" style="color:#999"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if review.thread %}
|
{% if review.thread %}
|
||||||
@ -112,11 +114,15 @@
|
|||||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
<i class="fas fa-thumbs-up mr-2"></i>
|
<i class="fas fa-thumbs-up mr-2"></i>
|
||||||
<input type="radio" name="recommends" value="yes" autocomplete="off"> {{ _("Yes") }}
|
<input type="radio" name="rating" value="5" autocomplete="off"> {{ _("Yes") }}
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-primary">
|
||||||
|
<i class="fas fa-minus mr-2"></i>
|
||||||
|
<input type="radio" name="rating" value="3" autocomplete="off"> {{ _("Neutral") }}
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary">
|
<label class="btn btn-primary">
|
||||||
<i class="fas fa-thumbs-down mr-2"></i>
|
<i class="fas fa-thumbs-down mr-2"></i>
|
||||||
<input type="radio" name="recommends" value="no" autocomplete="off"> {{ _("No") }}
|
<input type="radio" name="rating" value="1" autocomplete="off"> {{ _("No") }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -149,11 +155,15 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-primary" name="recommends" value="yes">
|
<button class="btn btn-primary" name="rating" value="5">
|
||||||
<i class="fas fa-thumbs-up mr-2"></i>
|
<i class="fas fa-thumbs-up mr-2"></i>
|
||||||
{{ _("Yes") }}
|
{{ _("Yes") }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" name="recommends" value="no">
|
<button class="btn btn-primary" name="rating" value="3">
|
||||||
|
<i class="fas fa-minus mr-2"></i>
|
||||||
|
{{ _("Neutral") }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" name="rating" value="1">
|
||||||
<i class="fas fa-thumbs-down mr-2"></i>
|
<i class="fas fa-thumbs-down mr-2"></i>
|
||||||
{{ _("No") }}
|
{{ _("No") }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -224,10 +224,12 @@
|
|||||||
<i class="fas fa-lock" style="color:#ffac33;"></i>
|
<i class="fas fa-lock" style="color:#ffac33;"></i>
|
||||||
{% elif not t.review %}
|
{% elif not t.review %}
|
||||||
<i class="fas fa-comment-alt" style="color:#666;"></i>
|
<i class="fas fa-comment-alt" style="color:#666;"></i>
|
||||||
{% elif t.review.recommends %}
|
{% elif t.review.rating > 3 %}
|
||||||
<i class="fas fa-thumbs-up" style="color:#6f6;"></i>
|
<i class="fas fa-thumbs-up" style="color:#6f6;"></i>
|
||||||
{% else %}
|
{% elif t.review.rating < 3 %}
|
||||||
<i class="fas fa-thumbs-down" style="color:#f66;"></i>
|
<i class="fas fa-thumbs-down" style="color:#f66;"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-minus" style="color:#999"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<strong class="ml-1">
|
<strong class="ml-1">
|
||||||
{{ t.title }}
|
{{ t.title }}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
||||||
</p>
|
</p>
|
||||||
{{ 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"}) }}
|
||||||
|
|
||||||
<p class="mt-4 mb-3">
|
<p class="mt-4 mb-3">
|
||||||
{{ _("Why or why not? Try to be constructive") }}
|
{{ _("Why or why not? Try to be constructive") }}
|
||||||
|
@ -54,10 +54,12 @@
|
|||||||
{% for review in reviews %}
|
{% for review in reviews %}
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">
|
<th colspan="2">
|
||||||
{% if review.recommends %}
|
{% if review.rating > 3 %}
|
||||||
<i class="fas fa-thumbs-up text-success mr-2"></i>
|
<i class="fas fa-thumbs-up text-success mr-2"></i>
|
||||||
{% else %}
|
{% elif review.rating < 3 %}
|
||||||
<i class="fas fa-thumbs-down text-danger mr-2"></i>
|
<i class="fas fa-thumbs-down text-danger mr-2"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-minus mr-2"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ review.thread.getViewURL() }}">
|
<a href="{{ review.thread.getViewURL() }}">
|
||||||
{{ review.thread.title }}
|
{{ review.thread.title }}
|
||||||
@ -86,4 +88,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -198,9 +198,11 @@
|
|||||||
<a class="btn" href="#reviews" title="{{ _("Reviews") }}">
|
<a class="btn" href="#reviews" title="{{ _("Reviews") }}">
|
||||||
<i class="fas fa-star-half-alt"></i>
|
<i class="fas fa-star-half-alt"></i>
|
||||||
<span class="count">
|
<span class="count">
|
||||||
+{{ 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 }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
{% if package.website %}
|
{% if package.website %}
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
{% block title %}
|
{% block title %}
|
||||||
{%- if thread.package -%}
|
{%- if thread.package -%}
|
||||||
{%- if thread.review -%}
|
{%- if thread.review -%}
|
||||||
{%- if thread.review.recommends -%}
|
{%- if thread.review.rating > 3 -%}
|
||||||
{%- set rating = "👍" -%}
|
{%- set rating = "👍" -%}
|
||||||
{%- else -%}
|
{%- elif thread.review.rating < 3 -%}
|
||||||
{%- set rating = "👎" -%}
|
{%- set rating = "👎" -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- set rating = "-" -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{{ rating }} {{ thread.title }} - {{ thread.package.title }}
|
{{ rating }} {{ thread.title }} - {{ thread.package.title }}
|
||||||
@ -70,10 +72,12 @@
|
|||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
{% if thread.review %}
|
{% if thread.review %}
|
||||||
{% if thread.review.recommends %}
|
{% if thread.review.rating > 3 %}
|
||||||
<i class="fas fa-thumbs-up mr-2" style="color:#6f6;"></i>
|
<i class="fas fa-thumbs-up mr-2" style="color:#6f6;"></i>
|
||||||
{% else %}
|
{% elif thread.review.rating < 3 %}
|
||||||
<i class="fas fa-thumbs-down mr-2" style="color:#f66;"></i>
|
<i class="fas fa-thumbs-down mr-2" style="color:#f66;"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-minus mr-2" style="color:#999"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if thread.private %}🔒 {% endif %}{{ thread.title }}
|
{% if thread.private %}🔒 {% endif %}{{ thread.title }}
|
||||||
|
37
migrations/versions/dabd7ab14339_.py
Normal file
37
migrations/versions/dabd7ab14339_.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user