mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 22:12:24 +01:00
Add Featured packages and protected tags
This commit is contained in:
parent
ce2bb3abad
commit
148ece162c
@ -44,6 +44,7 @@ class TagForm(FlaskForm):
|
|||||||
title = StringField("Title", [InputRequired(), Length(3,100)])
|
title = StringField("Title", [InputRequired(), Length(3,100)])
|
||||||
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, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
||||||
|
is_protected = BooleanField("Is Protected")
|
||||||
submit = SubmitField("Save")
|
submit = SubmitField("Save")
|
||||||
|
|
||||||
@bp.route("/tags/new/", methods=["GET", "POST"])
|
@bp.route("/tags/new/", methods=["GET", "POST"])
|
||||||
@ -59,14 +60,16 @@ def create_edit_tag(name=None):
|
|||||||
if not Permission.checkPerm(current_user, Permission.EDIT_TAGS if tag else Permission.CREATE_TAG):
|
if not Permission.checkPerm(current_user, Permission.EDIT_TAGS if tag else Permission.CREATE_TAG):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
form = TagForm(formdata=request.form, obj=tag)
|
form = TagForm( obj=tag)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
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)
|
||||||
else:
|
else:
|
||||||
form.populate_obj(tag)
|
form.populate_obj(tag)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
if Permission.EDIT_TAGS.check(current_user):
|
if Permission.EDIT_TAGS.check(current_user):
|
||||||
|
@ -364,6 +364,8 @@ def homepage():
|
|||||||
query = Package.query.filter_by(state=PackageState.APPROVED)
|
query = Package.query.filter_by(state=PackageState.APPROVED)
|
||||||
count = query.count()
|
count = query.count()
|
||||||
|
|
||||||
|
featured = Package.query.filter(Package.tags.any(name="featured")).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()
|
||||||
@ -386,6 +388,7 @@ def homepage():
|
|||||||
return {
|
return {
|
||||||
"count": count,
|
"count": count,
|
||||||
"downloads": downloads,
|
"downloads": downloads,
|
||||||
|
"featured": featured,
|
||||||
"new": mapPackages(new),
|
"new": mapPackages(new),
|
||||||
"updated": mapPackages(updated),
|
"updated": mapPackages(updated),
|
||||||
"pop_mod": mapPackages(pop_mod),
|
"pop_mod": mapPackages(pop_mod),
|
||||||
|
@ -18,6 +18,8 @@ def home():
|
|||||||
query = Package.query.filter_by(state=PackageState.APPROVED)
|
query = Package.query.filter_by(state=PackageState.APPROVED)
|
||||||
count = query.count()
|
count = query.count()
|
||||||
|
|
||||||
|
featured = Package.query.filter(Package.tags.any(name="featured")).order_by(func.random()).limit(6).all()
|
||||||
|
|
||||||
new = join(query.order_by(db.desc(Package.approved_at))).limit(4).all()
|
new = join(query.order_by(db.desc(Package.approved_at))).limit(4).all()
|
||||||
pop_mod = join(query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score))).limit(8).all()
|
pop_mod = join(query.filter_by(type=PackageType.MOD).order_by(db.desc(Package.score))).limit(8).all()
|
||||||
pop_gam = join(query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score))).limit(8).all()
|
pop_gam = join(query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score))).limit(8).all()
|
||||||
@ -39,5 +41,5 @@ def home():
|
|||||||
tags = db.session.query(func.count(Tags.c.tag_id), Tag) \
|
tags = db.session.query(func.count(Tags.c.tag_id), Tag) \
|
||||||
.select_from(Tag).outerjoin(Tags).group_by(Tag.id).order_by(db.asc(Tag.title)).all()
|
.select_from(Tag).outerjoin(Tags).group_by(Tag.id).order_by(db.asc(Tag.title)).all()
|
||||||
|
|
||||||
return render_template("index.html", count=count, downloads=downloads, tags=tags,
|
return render_template("index.html", count=count, downloads=downloads, tags=tags, featured=featured,
|
||||||
new=new, updated=updated, pop_mod=pop_mod, pop_txp=pop_txp, pop_gam=pop_gam, high_reviewed=high_reviewed, reviews=reviews)
|
new=new, updated=updated, pop_mod=pop_mod, pop_txp=pop_txp, pop_gam=pop_gam, high_reviewed=high_reviewed, reviews=reviews)
|
||||||
|
@ -19,7 +19,7 @@ import re
|
|||||||
import validators
|
import validators
|
||||||
|
|
||||||
from app.logic.LogicError import LogicError
|
from app.logic.LogicError import LogicError
|
||||||
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, AuditSeverity, License
|
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, AuditSeverity, License, UserRank
|
||||||
from app.utils import addAuditLog
|
from app.utils import addAuditLog
|
||||||
|
|
||||||
|
|
||||||
@ -134,14 +134,19 @@ def do_edit_package(user: User, package: Package, was_new: bool, data: dict, rea
|
|||||||
package.provides.append(m)
|
package.provides.append(m)
|
||||||
|
|
||||||
if "tags" in data:
|
if "tags" in data:
|
||||||
|
old_tags = package.tags
|
||||||
package.tags.clear()
|
package.tags.clear()
|
||||||
for tag_id in data["tags"]:
|
for tag_id in data["tags"]:
|
||||||
if is_int(tag_id):
|
if is_int(tag_id):
|
||||||
package.tags.append(Tag.query.get(tag_id))
|
tag = Tag.query.get(tag_id)
|
||||||
else:
|
else:
|
||||||
tag = Tag.query.filter_by(name=tag_id).first()
|
tag = Tag.query.filter_by(name=tag_id).first()
|
||||||
if tag is None:
|
if tag is None:
|
||||||
raise LogicError(400, "Unknown tag: " + tag_id)
|
raise LogicError(400, "Unknown tag: " + tag_id)
|
||||||
|
|
||||||
|
if tag.is_protected and tag not in old_tags and not user.rank.atLeast(UserRank.EDITOR):
|
||||||
|
raise LogicError(400, f"Unable to add protected tag {tag.title} to package")
|
||||||
|
|
||||||
package.tags.append(tag)
|
package.tags.append(tag)
|
||||||
|
|
||||||
if "content_warnings" in data:
|
if "content_warnings" in data:
|
||||||
|
@ -744,6 +744,7 @@ 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)
|
||||||
|
|
||||||
|
@ -173,4 +173,12 @@ pre code {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fs-2 {
|
||||||
|
font-size: calc(1.325rem + .9vw) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-shadow {
|
||||||
|
text-shadow: 3px 3px 3px rgba(10,10,10,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
@import "dracula.scss";
|
@import "dracula.scss";
|
||||||
|
@ -70,6 +70,23 @@ def getMeta(urlstr, author):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_edit_data_from_dir(dir: str):
|
||||||
|
data = {}
|
||||||
|
for path in [os.path.join(dir, ".cdb.json"), os.path.join(dir, ".cdb", "meta.json")]:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, "r") as f:
|
||||||
|
data = json.loads(f.read())
|
||||||
|
break
|
||||||
|
|
||||||
|
for path in [os.path.join(dir, ".cdb.md"), os.path.join(dir, ".cdb", "long_description.md")]:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, "r") as f:
|
||||||
|
data["long_description"] = f.read().replace("\r\n", "\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def postReleaseCheckUpdate(self, release: PackageRelease, path):
|
def postReleaseCheckUpdate(self, release: PackageRelease, path):
|
||||||
try:
|
try:
|
||||||
tree = build_tree(path, expected_type=ContentType[release.package.type.name],
|
tree = build_tree(path, expected_type=ContentType[release.package.type.name],
|
||||||
@ -117,13 +134,11 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
|
|||||||
release.max_rel = MinetestRelease.get(tree.meta["max_minetest_version"], None)
|
release.max_rel = MinetestRelease.get(tree.meta["max_minetest_version"], None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(tree.baseDir, ".cdb.json"), "r") as f:
|
data = get_edit_data_from_dir(tree.baseDir)
|
||||||
data = json.loads(f.read())
|
if data != {}: # Not sure if this will actually work to check not empty, probably not
|
||||||
do_edit_package(package.author, package, False, data, "Post release hook")
|
do_edit_package(package.author, package, False, data, "Post release hook")
|
||||||
except LogicError as e:
|
except LogicError as e:
|
||||||
raise TaskError(e.message)
|
raise TaskError(e.message)
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<a class="btn btn-primary float-right" href="{{ url_for('admin.create_edit_tag') }}">New Tag</a>
|
<a class="btn btn-primary float-right" href="{{ url_for('admin.create_edit_tag') }}">New Tag</a>
|
||||||
<a class="btn btn-secondary mb-4" href="{{ url_for('admin.tag_list') }}">Back to list</a>
|
<a class="btn btn-secondary mb-4" href="{{ url_for('admin.tag_list') }}">Back to list</a>
|
||||||
|
|
||||||
{% from "macros/forms.html" import render_field, render_submit_field %}
|
{% from "macros/forms.html" import render_field, render_submit_field, render_checkbox_field %}
|
||||||
<form method="POST" action="" enctype="multipart/form-data">
|
<form method="POST" action="" enctype="multipart/form-data">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
@ -21,6 +21,10 @@
|
|||||||
{% 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) }}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -21,7 +21,69 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% from "macros/packagegridtile.html" import render_pkggrid %}
|
{% from "macros/packagegridtile.html" import render_pkggrid %}
|
||||||
|
<div id="featuredCarousel" class="carousel slide mb-5" data-ride="carousel">
|
||||||
|
<ol class="carousel-indicators">
|
||||||
|
{% for package in featured %}
|
||||||
|
<li data-target="#featuredCarousel" data-slide-to="{{ loop.index - 1 }}" {% if loop.index == 1 %}class="active"{% endif %}></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
<div class="carousel-inner">
|
||||||
|
{% for package in featured %}
|
||||||
|
{% set cover_image = package.cover_image.url or package.getMainScreenshotURL() %}
|
||||||
|
{% set tags = package.tags | sort(attribute="views", reverse=True) %}
|
||||||
|
<div class="carousel-item {% if loop.index == 1 %}active{% endif %}">
|
||||||
|
<a href="{{ package.getDetailsURL() }}">
|
||||||
|
<div class="embed-responsive embed-responsive-16by9">
|
||||||
|
<img class="embed-responsive-item" src="{{ cover_image }}"
|
||||||
|
alt="{{ _('%(title)s by %(author)s', title=package.title, author=package.author.display_name) }}">
|
||||||
|
</div>
|
||||||
|
<div class="carousel-caption d-none d-md-block text-shadow">
|
||||||
|
<h3 class="mt-0 mb-3 fs-2">
|
||||||
|
{{ _('<strong>%(title)s</strong> by %(author)s', title=package.title, author=package.author.display_name) }}
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
{{ package.short_desc }}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<span class="mr-2">
|
||||||
|
{{ package.type.value }}
|
||||||
|
</span>
|
||||||
|
{% for warning in package.content_warnings %}
|
||||||
|
<span class="badge badge-warning" title="{{ warning.description }}">
|
||||||
|
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||||
|
{{ warning.title }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% for t in tags[:3] %}
|
||||||
|
{% if t.name != "featured" %}
|
||||||
|
<span class="badge badge-primary" title="{{ t.description or '' }}">
|
||||||
|
{{ t.title }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<span class="btn" title="{{ _("Reviews") }}">
|
||||||
|
<i class="fas fa-star-half-alt"></i>
|
||||||
|
<span class="count">
|
||||||
|
+{{ package.reviews | selectattr("recommends") | list | length }}
|
||||||
|
/
|
||||||
|
-{{ package.reviews | rejectattr("recommends") | list | length }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<a class="carousel-control-prev" href="#featuredCarousel" role="button" data-slide="prev">
|
||||||
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">{{ _("Previous") }}</span>
|
||||||
|
</a>
|
||||||
|
<a class="carousel-control-next" href="#featuredCarousel" role="button" data-slide="next">
|
||||||
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">{{ _("Next") }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ url_for('packages.list_all', sort='approved_at', order='desc') }}" class="btn btn-secondary float-right">
|
<a href="{{ url_for('packages.list_all', sort='approved_at', order='desc') }}" class="btn btn-secondary float-right">
|
||||||
{{ _("See more") }}
|
{{ _("See more") }}
|
||||||
@ -76,7 +138,7 @@
|
|||||||
<a href="{{ url_for('packages.list_all', sort='reviews', order='desc') }}" class="btn btn-secondary float-right">
|
<a href="{{ url_for('packages.list_all', sort='reviews', order='desc') }}" class="btn btn-secondary float-right">
|
||||||
{{ _("See more") }}
|
{{ _("See more") }}
|
||||||
</a>
|
</a>
|
||||||
<h2 class="my-3">{{ _("Top Reviewed") }}</h2>
|
<h2 class="my-3">{{ _("Highest Reviewed") }}</h2>
|
||||||
{{ render_pkggrid(high_reviewed) }}
|
{{ render_pkggrid(high_reviewed) }}
|
||||||
|
|
||||||
|
|
||||||
|
28
migrations/versions/d4262fb15b37_.py
Normal file
28
migrations/versions/d4262fb15b37_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: d4262fb15b37
|
||||||
|
Revises: 8ee3cf3fb312
|
||||||
|
Create Date: 2021-07-22 10:59:03.217264
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'd4262fb15b37'
|
||||||
|
down_revision = '8ee3cf3fb312'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('tag', sa.Column('is_protected', sa.Boolean(), nullable=False, server_default="false"))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('tag', 'is_protected')
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Reference in New Issue
Block a user