Use collections for spotlight and featured, remove protected tags

This commit is contained in:
rubenwardy 2023-08-20 22:25:18 +01:00
parent a2ea6573bd
commit 4bd53e4b1a
10 changed files with 49 additions and 38 deletions

@ -47,7 +47,6 @@ class TagForm(FlaskForm):
description = TextAreaField("Description", [Optional(), Length(0, 500)]) description = TextAreaField("Description", [Optional(), Length(0, 500)])
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0,
"Lower case letters (a-z), digits (0-9), and underscores (_) only")]) "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
is_protected = BooleanField("Is Protected")
submit = SubmitField("Save") submit = SubmitField("Save")
@ -69,7 +68,6 @@ def create_edit_tag(name=None):
if tag is None: if tag is None:
tag = Tag(form.title.data) tag = Tag(form.title.data)
tag.description = form.description.data tag.description = form.description.data
tag.is_protected = form.is_protected.data
db.session.add(tag) db.session.add(tag)
add_audit_log(AuditSeverity.EDITOR, current_user, f"Created tag {tag.name}", add_audit_log(AuditSeverity.EDITOR, current_user, f"Created tag {tag.name}",

@ -21,6 +21,7 @@ from typing import List
import flask_sqlalchemy import flask_sqlalchemy
from flask import request, jsonify, current_app, Response from flask import request, jsonify, current_app, Response
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy import and_
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
@ -28,7 +29,7 @@ from app import csrf
from app.logic.graphs import get_package_stats, get_package_stats_for_user, get_all_package_stats from app.logic.graphs import get_package_stats, get_package_stats_for_user, get_all_package_stats
from app.markdown import render_markdown from app.markdown import render_markdown
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, \ from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, \
MinetestRelease, APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread MinetestRelease, APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread, Collection
from app.querybuilder import QueryBuilder from app.querybuilder import QueryBuilder
from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, is_yes, get_request_date from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, is_yes, get_request_date
from . import bp from . import bp
@ -78,7 +79,8 @@ def packages():
# Promote featured packages # Promote featured packages
if "sort" not in request.args and "order" not in request.args and "q" not in request.args: if "sort" not in request.args and "order" not in request.args and "q" not in request.args:
featured_lut = set() featured_lut = set()
featured = qb.convert_to_dictionary(query.filter(Package.tags.any(name="featured")).all()) featured = qb.convert_to_dictionary(query.filter(
Package.collections.any(and_(Collection.name == "featured", Collection.author.has(username="ContentDB")))).all())
for pkg in featured: for pkg in featured:
featured_lut.add(f"{pkg['author']}/{pkg['name']}") featured_lut.add(f"{pkg['author']}/{pkg['name']}")
pkg["short_description"] = "Featured. " + pkg["short_description"] pkg["short_description"] = "Featured. " + pkg["short_description"]
@ -535,8 +537,9 @@ def homepage():
query = Package.query.filter_by(state=PackageState.APPROVED) query = Package.query.filter_by(state=PackageState.APPROVED)
count = query.count() count = query.count()
spotlight = query.filter(Package.tags.any(name="spotlight")).order_by( spotlight = query.filter(
func.random()).limit(6).all() Package.collections.any(and_(Collection.name == "featured", Collection.author.has(username="ContentDB")))) \
.order_by(func.random()).limit(6).all()
new = query.order_by(db.desc(Package.approved_at)).limit(4).all() new = query.order_by(db.desc(Package.approved_at)).limit(4).all()
pop_mod = query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score)).limit(8).all() pop_mod = query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score)).limit(8).all()
pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(8).all() pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(8).all()
@ -574,7 +577,8 @@ def homepage():
def welcome_v1(): def welcome_v1():
featured = Package.query \ featured = Package.query \
.filter(Package.type == PackageType.GAME, Package.state == PackageState.APPROVED, .filter(Package.type == PackageType.GAME, Package.state == PackageState.APPROVED,
Package.tags.any(name="featured")) \ Package.collections.any(
and_(Collection.name == "featured", Collection.author.has(username="ContentDB")))) \
.order_by(func.random()) \ .order_by(func.random()) \
.limit(5).all() .limit(5).all()

@ -15,8 +15,10 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from flask import Blueprint, render_template, redirect from flask import Blueprint, render_template, redirect
from sqlalchemy import and_
from app.models import Package, PackageReview, Thread, User, PackageState, db, PackageType, PackageRelease, Tags, Tag from app.models import Package, PackageReview, Thread, User, PackageState, db, PackageType, PackageRelease, Tags, Tag, \
Collection
bp = Blueprint("homepage", __name__) bp = Blueprint("homepage", __name__)
@ -49,7 +51,9 @@ def home():
query = Package.query.filter_by(state=PackageState.APPROVED) query = Package.query.filter_by(state=PackageState.APPROVED)
count = query.count() count = query.count()
spotlight_pkgs = query.filter(Package.tags.any(name="spotlight")).order_by(func.random()).limit(6).all() spotlight_pkgs = query.filter(
Package.collections.any(and_(Collection.name == "featured", Collection.author.has(username="ContentDB")))) \
.order_by(func.random()).limit(6).all()
new = package_load(query.order_by(db.desc(Package.approved_at))).limit(4).all() new = package_load(query.order_by(db.desc(Package.approved_at))).limit(4).all()
pop_mod = package_load(query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score))).limit(8).all() pop_mod = package_load(query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score))).limit(8).all()

@ -44,18 +44,17 @@ def populate(session):
tags = {} tags = {}
for tag in ["Inventory", "Mapgen", "Building", for tag in ["Inventory", "Mapgen", "Building",
"Mobs and NPCs", "Tools", "Player effects", "Mobs and NPCs", "Tools", "Player effects",
"Environment", "Transport", "Maintenance", "Plants and farming", "Environment", "Transport", "Maintenance", "Plants and farming",
"PvP", "PvE", "Survival", "Creative", "Puzzle", "Multiplayer", "Singleplayer", "PvP", "PvE", "Survival", "Creative", "Puzzle", "Multiplayer", "Singleplayer"]:
"Featured", "Spotlight"]:
row = Tag(tag) row = Tag(tag)
tags[row.name] = row tags[row.name] = row
session.add(row) session.add(row)
licenses = {} licenses = {}
for license in ["GPLv2.1", "GPLv3", "LGPLv2.1", "LGPLv3", "AGPLv2.1", "AGPLv3", for license in ["GPLv2.1", "GPLv3", "LGPLv2.1", "LGPLv3", "AGPLv2.1", "AGPLv3",
"Apache", "BSD 3-Clause", "BSD 2-Clause", "CC0", "CC-BY-SA", "Apache", "BSD 3-Clause", "BSD 2-Clause", "CC0", "CC-BY-SA",
"CC-BY", "MIT", "ZLib", "Other (Free)"]: "CC-BY", "MIT", "ZLib", "Other (Free)"]:
row = License(license) row = License(license)
licenses[row.name] = row licenses[row.name] = row
session.add(row) session.add(row)

@ -451,7 +451,6 @@ Supported query parameters:
* `name`: technical name. * `name`: technical name.
* `title`: human-readable title. * `title`: human-readable title.
* `description`: tag description or null. * `description`: tag description or null.
* `is_protected`: boolean, whether the tag is protected (can only be set by Editors in the web interface).
* `views`: number of views of this tag. * `views`: number of views of this tag.
### Content Warnings ### Content Warnings

@ -170,19 +170,8 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
if tag is None: if tag is None:
raise LogicError(400, "Unknown tag: " + tag_id) raise LogicError(400, "Unknown tag: " + tag_id)
if not was_web and tag.is_protected:
continue
if tag.is_protected and tag not in old_tags and not user.rank.at_least(UserRank.EDITOR):
raise LogicError(400, lazy_gettext("Unable to add protected tag %(title)s to package", title=tag.title))
package.tags.append(tag) package.tags.append(tag)
if not was_web:
for tag in old_tags:
if tag.is_protected:
package.tags.append(tag)
if "content_warnings" in data: if "content_warnings" in data:
package.content_warnings.clear() package.content_warnings.clear()
for warning_id in (data["content_warnings"] or []): for warning_id in (data["content_warnings"] or []):

@ -865,7 +865,6 @@ class Tag(db.Model):
backgroundColor = db.Column(db.String(6), nullable=False) backgroundColor = db.Column(db.String(6), nullable=False)
textColor = db.Column(db.String(6), nullable=False) textColor = db.Column(db.String(6), nullable=False)
views = db.Column(db.Integer, nullable=False, default=0) views = db.Column(db.Integer, nullable=False, default=0)
is_protected = db.Column(db.Boolean, nullable=False, default=False)
packages = db.relationship("Package", back_populates="tags", secondary=Tags) packages = db.relationship("Package", back_populates="tags", secondary=Tags)
@ -884,7 +883,6 @@ class Tag(db.Model):
"name": self.name, "name": self.name,
"title": self.title, "title": self.title,
"description": description, "description": description,
"is_protected": self.is_protected,
"views": self.views, "views": self.views,
} }

@ -21,10 +21,6 @@
{% if tag %} {% if tag %}
{{ render_field(form.name) }} {{ render_field(form.name) }}
{% endif %} {% endif %}
<div class="form-group my-5">
{{ render_checkbox_field(form.is_protected) }}
<small class="form-text text-muted">Whether non-Editors can add this tag to packages</small>
</div>
{{ render_submit_field(form.submit) }} {{ render_submit_field(form.submit) }}
{% if tag %} {% if tag %}
<a class="ml-5" target="_blank" href="{{ url_for('packages.list_all', tag=tag.name) }}"> <a class="ml-5" target="_blank" href="{{ url_for('packages.list_all', tag=tag.name) }}">

@ -64,11 +64,9 @@
</span> </span>
{% endfor %} {% endfor %}
{% for t in tags[:3] %} {% for t in tags[:3] %}
{% if t.name != "featured" and t.name != "Spotlight" %} <span class="badge badge-primary" title="{{ t.description or '' }}">
<span class="badge badge-primary" title="{{ t.description or '' }}"> {{ t.title }}
{{ t.title }} </span>
</span>
{% endif %}
{% endfor %} {% endfor %}
<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>

@ -0,0 +1,26 @@
"""empty message
Revision ID: 7a749a6c8c3a
Revises: 20f2aa2f40b9
Create Date: 2023-08-20 21:19:26.930069
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '7a749a6c8c3a'
down_revision = '20f2aa2f40b9'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('tag', schema=None) as batch_op:
batch_op.drop_column('is_protected')
def downgrade():
with op.batch_alter_table('tag', schema=None) as batch_op:
batch_op.add_column(sa.Column('is_protected', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))