mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 22:12:24 +01:00
parent
d5492cbb9b
commit
e75f2f92e2
@ -84,4 +84,4 @@ def get_package_tabs(user: User, package: Package):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
from . import packages, screenshots, releases, reviews, game_hub
|
from . import packages, advanced_search, screenshots, releases, reviews, game_hub
|
||||||
|
92
app/blueprints/packages/advanced_search.py
Normal file
92
app/blueprints/packages/advanced_search.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# ContentDB
|
||||||
|
# Copyright (C) 2024 rubenwardy
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
|
from flask_babel import lazy_gettext, gettext
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms.fields.choices import SelectField, SelectMultipleField
|
||||||
|
from wtforms.fields.simple import StringField, BooleanField
|
||||||
|
from wtforms.validators import Optional
|
||||||
|
from wtforms_sqlalchemy.fields import QuerySelectMultipleField, QuerySelectField
|
||||||
|
|
||||||
|
from . import bp
|
||||||
|
from ...models import PackageType, Tag, db, ContentWarning, License, Language, MinetestRelease
|
||||||
|
|
||||||
|
|
||||||
|
def make_label(obj: Tag | ContentWarning):
|
||||||
|
translated = obj.get_translated()
|
||||||
|
if translated["description"]:
|
||||||
|
return "{}: {}".format(translated["title"], translated["description"])
|
||||||
|
else:
|
||||||
|
return translated["title"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_hide_choices():
|
||||||
|
ret = [
|
||||||
|
("android_default", gettext("Android Default")),
|
||||||
|
("desktop_default", gettext("Desktop Default")),
|
||||||
|
("nonfree", gettext("Non-free")),
|
||||||
|
("wip", gettext("Work in Progress")),
|
||||||
|
("deprecated", gettext("Deprecated")),
|
||||||
|
("*", gettext("All content warnings")),
|
||||||
|
]
|
||||||
|
content_warnings = ContentWarning.query.order_by(db.asc(ContentWarning.name)).all()
|
||||||
|
tags = Tag.query.order_by(db.asc(Tag.name)).all()
|
||||||
|
ret += [(x.name, make_label(x)) for x in content_warnings + tags]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class AdvancedSearchForm(FlaskForm):
|
||||||
|
q = StringField(lazy_gettext("Query"), [Optional()])
|
||||||
|
type = SelectMultipleField(lazy_gettext("Type"), [Optional()], choices=PackageType.choices(),
|
||||||
|
coerce=PackageType.coerce)
|
||||||
|
author = StringField(lazy_gettext("Author"), [Optional()])
|
||||||
|
tag = QuerySelectMultipleField(lazy_gettext('Tags'),
|
||||||
|
query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)),
|
||||||
|
get_pk=lambda a: a.id, get_label=make_label)
|
||||||
|
flag = QuerySelectMultipleField(lazy_gettext('Content Warnings'), query_factory=lambda: ContentWarning.query.order_by(db.asc(ContentWarning.name)), get_pk=lambda a: a.id, get_label=make_label)
|
||||||
|
license = QuerySelectMultipleField(lazy_gettext("License"), [Optional()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
|
||||||
|
game = StringField(lazy_gettext("Supports Game"), [Optional()])
|
||||||
|
lang = QuerySelectField(lazy_gettext("Language"), allow_blank=True,
|
||||||
|
query_factory=lambda: Language.query.order_by(db.asc(Language.title)),
|
||||||
|
get_pk=lambda a: a.id, get_label=lambda a: a.title)
|
||||||
|
hide = SelectMultipleField(lazy_gettext("Hide Tags and Content Warnings"), [Optional()])
|
||||||
|
engine_version = QuerySelectField(lazy_gettext("Minetest Version"), allow_blank=True,
|
||||||
|
query_factory=lambda: MinetestRelease.query.order_by(db.asc(MinetestRelease.id)),
|
||||||
|
get_pk=lambda a: a.value, get_label=lambda a: a.name)
|
||||||
|
sort = SelectField(lazy_gettext("Sort by"), [Optional()], choices=[
|
||||||
|
("", ""),
|
||||||
|
("name", lazy_gettext("Name")),
|
||||||
|
("title", lazy_gettext("Title")),
|
||||||
|
("score", lazy_gettext("Package score")),
|
||||||
|
("reviews", lazy_gettext("Reviews")),
|
||||||
|
("downloads", lazy_gettext("Downloads")),
|
||||||
|
("created_at", lazy_gettext("Created At")),
|
||||||
|
("approved_at", lazy_gettext("Approved At")),
|
||||||
|
("last_release", lazy_gettext("Last Release")),
|
||||||
|
])
|
||||||
|
order = SelectField(lazy_gettext("Order"), [Optional()], choices=[
|
||||||
|
("desc", lazy_gettext("Descending")),
|
||||||
|
("asc", lazy_gettext("Ascending")),
|
||||||
|
])
|
||||||
|
random = BooleanField(lazy_gettext("Random order"))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/packages/advanced-search/")
|
||||||
|
def advanced_search():
|
||||||
|
form = AdvancedSearchForm()
|
||||||
|
form.hide.choices = get_hide_choices()
|
||||||
|
return render_template("packages/advanced_search.html", form=form)
|
@ -126,7 +126,7 @@ class PackageType(enum.Enum):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def choices(cls):
|
def choices(cls):
|
||||||
return [(choice, choice.text) for choice in cls]
|
return [(choice.name.lower(), choice.text) for choice in cls]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coerce(cls, item):
|
def coerce(cls, item):
|
||||||
@ -853,7 +853,7 @@ class Package(db.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def recalculate_score(self):
|
def recalculate_score(self):
|
||||||
review_scores = [ 100 * r.as_weight() for r in self.reviews ]
|
review_scores = [ 150 * r.as_weight() for r in self.reviews ]
|
||||||
self.score = self.score_downloads + sum(review_scores)
|
self.score = self.score_downloads + sum(review_scores)
|
||||||
|
|
||||||
def get_conf_file_name(self):
|
def get_conf_file_name(self):
|
||||||
@ -1042,6 +1042,10 @@ class MinetestRelease(db.Model):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
def get_actual(self):
|
def get_actual(self):
|
||||||
return None if self.name == "None" else self
|
return None if self.name == "None" else self
|
||||||
|
|
||||||
|
@ -138,6 +138,8 @@ class QueryBuilder:
|
|||||||
self.lucky = "lucky" in args
|
self.lucky = "lucky" in args
|
||||||
self.limit = 1 if self.lucky else get_int_or_abort(args.get("limit"), None)
|
self.limit = 1 if self.lucky else get_int_or_abort(args.get("limit"), None)
|
||||||
self.order_by = args.get("sort")
|
self.order_by = args.get("sort")
|
||||||
|
if self.order_by == "":
|
||||||
|
self.order_by = None
|
||||||
self.order_dir = args.get("order") or "desc"
|
self.order_dir = args.get("order") or "desc"
|
||||||
|
|
||||||
if "android_default" in self.hide_flags:
|
if "android_default" in self.hide_flags:
|
||||||
@ -161,6 +163,9 @@ class QueryBuilder:
|
|||||||
|
|
||||||
protocol_version = get_int_or_abort(args.get("protocol_version"))
|
protocol_version = get_int_or_abort(args.get("protocol_version"))
|
||||||
minetest_version = args.get("engine_version")
|
minetest_version = args.get("engine_version")
|
||||||
|
if minetest_version == "__None":
|
||||||
|
minetest_version = None
|
||||||
|
|
||||||
if protocol_version or minetest_version:
|
if protocol_version or minetest_version:
|
||||||
self.version = MinetestRelease.get(minetest_version, protocol_version)
|
self.version = MinetestRelease.get(minetest_version, protocol_version)
|
||||||
else:
|
else:
|
||||||
@ -176,8 +181,14 @@ class QueryBuilder:
|
|||||||
self.game = args.get("game")
|
self.game = args.get("game")
|
||||||
if self.game:
|
if self.game:
|
||||||
self.game = Package.get_by_key(self.game)
|
self.game = Package.get_by_key(self.game)
|
||||||
|
if self.game is None:
|
||||||
|
abort(make_response("Unable to find that game"), 400)
|
||||||
|
else:
|
||||||
|
self.game = None
|
||||||
|
|
||||||
self.has_lang = args.get("lang")
|
self.has_lang = args.get("lang")
|
||||||
|
if self.has_lang == "__None":
|
||||||
|
self.has_lang = None
|
||||||
|
|
||||||
if cookies and request.cookies.get("hide_nonfree") == "1":
|
if cookies and request.cookies.get("hide_nonfree") == "1":
|
||||||
self.hide_nonfree = True
|
self.hide_nonfree = True
|
||||||
@ -244,7 +255,7 @@ class QueryBuilder:
|
|||||||
if self.game:
|
if self.game:
|
||||||
query = query.filter(Package.supported_games.any(game=self.game, supports=True))
|
query = query.filter(Package.supported_games.any(game=self.game, supports=True))
|
||||||
|
|
||||||
if self.has_lang:
|
if self.has_lang and self.has_lang != "en":
|
||||||
query = query.filter(Package.translations.any(language_id=self.has_lang))
|
query = query.filter(Package.translations.any(language_id=self.has_lang))
|
||||||
|
|
||||||
for tag in self.tags:
|
for tag in self.tags:
|
||||||
|
@ -253,6 +253,7 @@
|
|||||||
{% if request.endpoint != "flatpage" and request.endpoint != "report.report" %}
|
{% if request.endpoint != "flatpage" and request.endpoint != "report.report" %}
|
||||||
<li class="list-inline-item"><a href="{{ url_for('report.report', url=url_current()) }}">{{ _("Report / DMCA") }}</a></li>
|
<li class="list-inline-item"><a href="{{ url_for('report.report', url=url_current()) }}">{{ _("Report / DMCA") }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li class="list-inline-item"><a href="{{ url_for('packages.advanced_search') }}">{{ _("Advanced Search") }}</a></li>
|
||||||
<li class="list-inline-item"><a href="{{ url_for('users.list_all') }}">{{ _("User List") }}</a></li>
|
<li class="list-inline-item"><a href="{{ url_for('users.list_all') }}">{{ _("User List") }}</a></li>
|
||||||
<li class="list-inline-item"><a href="{{ url_for('threads.list_all') }}">{{ _("Threads") }}</a></li>
|
<li class="list-inline-item"><a href="{{ url_for('threads.list_all') }}">{{ _("Threads") }}</a></li>
|
||||||
<li class="list-inline-item"><a href="{{ url_for('collections.list_all') }}">{{ _("Collections") }}</a></li>
|
<li class="list-inline-item"><a href="{{ url_for('collections.list_all') }}">{{ _("Collections") }}</a></li>
|
||||||
|
36
app/templates/packages/advanced_search.html
Normal file
36
app/templates/packages/advanced_search.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ _("Advanced Search") }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ self.title() }}</h1>
|
||||||
|
|
||||||
|
{% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %}
|
||||||
|
<form method="get" action="{{ url_for('packages.list_all') }}">
|
||||||
|
{{ render_field(form.q) }}
|
||||||
|
{{ render_field(form.type, hint=_("Use shift to select multiple. Leave selection empty to match any type.")) }}
|
||||||
|
{{ render_field(form.license, hint=_("Use shift to select multiple.")) }}
|
||||||
|
{{ render_field(form.lang) }}
|
||||||
|
|
||||||
|
<h2>{{ _("Tags and Content Warnings") }}</h2>
|
||||||
|
|
||||||
|
{{ render_field(form.tag, hint=_("Use shift to select multiple.")) }}
|
||||||
|
{{ render_field(form.flag, hint=_("Use shift to select multiple.")) }}
|
||||||
|
{{ render_field(form.hide, hint=_("Use shift to select multiple.")) }}
|
||||||
|
|
||||||
|
<h2>{{ _("Compatibility") }}</h2>
|
||||||
|
|
||||||
|
{{ render_field(form.engine_version) }}
|
||||||
|
{{ render_field(form.game, placeholder=_("author/name"), pattern="\w+/\w+", hint=_("author/name")) }}
|
||||||
|
|
||||||
|
<h2>{{ _("Sorting") }}</h2>
|
||||||
|
|
||||||
|
{{ render_field(form.sort) }}
|
||||||
|
{{ render_field(form.order) }}
|
||||||
|
{{ render_checkbox_field(form.random) }}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-5">{{ _("Search") }}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user