tag-based navigation implemented, added in-line screenshot viewer, fixes #10
This commit is contained in:
parent
8df6ebd2e7
commit
21fe900cc8
|
@ -26,8 +26,8 @@ from sqlalchemy import or_, and_
|
||||||
|
|
||||||
from app.logic.game_support import GameSupportResolver
|
from app.logic.game_support import GameSupportResolver
|
||||||
from app.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
|
from app.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
|
||||||
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, PackageGameSupport
|
NotificationType, PackageUpdateConfig, License, UserRank, PackageGameSupport
|
||||||
from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
|
# from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
|
||||||
from app.tasks.importtasks import importRepoScreenshot, checkZipRelease, check_for_updates
|
from app.tasks.importtasks import importRepoScreenshot, checkZipRelease, check_for_updates
|
||||||
from app.tasks.appstreamtasks import importFromFlathub
|
from app.tasks.appstreamtasks import importFromFlathub
|
||||||
from app.utils import addNotification, get_system_user
|
from app.utils import addNotification, get_system_user
|
||||||
|
@ -90,20 +90,20 @@ def reimport_packages():
|
||||||
return redirect(url_for("todo.view_editor"))
|
return redirect(url_for("todo.view_editor"))
|
||||||
|
|
||||||
|
|
||||||
@action("Import forum topic list")
|
# @action("Import forum topic list")
|
||||||
def import_topic_list():
|
# def import_topic_list():
|
||||||
task = importTopicList.delay()
|
# task = importTopicList.delay()
|
||||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
# return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||||
|
|
||||||
@action("Import appstream from flathub")
|
@action("Import appstream from flathub")
|
||||||
def import_from_flathub():
|
def import_from_flathub():
|
||||||
task = importFromFlathub.delay()
|
task = importFromFlathub.delay()
|
||||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("todo.topics")))
|
return redirect(url_for("tasks.check", id=task.id, r=url_for("todo.topics")))
|
||||||
|
|
||||||
@action("Check all forum accounts")
|
# @action("Check all forum accounts")
|
||||||
def check_all_forum_accounts():
|
# def check_all_forum_accounts():
|
||||||
task = checkAllForumAccounts.delay()
|
# task = checkAllForumAccounts.delay()
|
||||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
# return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||||
|
|
||||||
|
|
||||||
@action("Import screenshots")
|
@action("Import screenshots")
|
||||||
|
@ -297,13 +297,12 @@ def delete_inactive_users():
|
||||||
@action("Send Video URL notification")
|
@action("Send Video URL notification")
|
||||||
def remind_video_url():
|
def remind_video_url():
|
||||||
users = User.query.filter(User.maintained_packages.any(
|
users = User.query.filter(User.maintained_packages.any(
|
||||||
and_(Package.video_url.is_(None), Package.type==PackageType.GAME, Package.state==PackageState.APPROVED)))
|
and_(Package.video_url.is_(None), Package.state==PackageState.APPROVED)))
|
||||||
system_user = get_system_user()
|
system_user = get_system_user()
|
||||||
for user in users:
|
for user in users:
|
||||||
packages = db.session.query(Package.title).filter(
|
packages = db.session.query(Package.title).filter(
|
||||||
or_(Package.author==user, Package.maintainers.any(User.id==user.id)),
|
or_(Package.author==user, Package.maintainers.any(User.id==user.id)),
|
||||||
Package.video_url.is_(None),
|
Package.video_url.is_(None),
|
||||||
Package.type == PackageType.GAME,
|
|
||||||
Package.state == PackageState.APPROVED) \
|
Package.state == PackageState.APPROVED) \
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,10 @@ from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from app import csrf
|
from app import csrf
|
||||||
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, Package, db, PackageRelease, Permission, ForumTopic, \
|
||||||
APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread
|
APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread
|
||||||
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, isYes
|
from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, isYes, get_toplevel_tags
|
||||||
from . import bp
|
from . import bp
|
||||||
from .auth import is_api_authd
|
from .auth import is_api_authd
|
||||||
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, \
|
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, \
|
||||||
|
@ -89,9 +89,6 @@ def resolve_package_deps(out, package, only_hard, depth=1):
|
||||||
ret = []
|
ret = []
|
||||||
out[id] = ret
|
out[id] = ret
|
||||||
|
|
||||||
if package.type != PackageType.TOOL:
|
|
||||||
return
|
|
||||||
|
|
||||||
for dep in package.dependencies:
|
for dep in package.dependencies:
|
||||||
if only_hard and dep.optional:
|
if only_hard and dep.optional:
|
||||||
continue
|
continue
|
||||||
|
@ -106,7 +103,7 @@ def resolve_package_deps(out, package, only_hard, depth=1):
|
||||||
fulfilled_by = [ pkg.getId() for pkg in dep.meta_package.packages]
|
fulfilled_by = [ pkg.getId() for pkg in dep.meta_package.packages]
|
||||||
|
|
||||||
if depth == 1 and not dep.optional:
|
if depth == 1 and not dep.optional:
|
||||||
most_likely = next((pkg for pkg in dep.meta_package.packages if pkg.type == PackageType.TOOL), None)
|
most_likely = next((pkg for pkg in dep.meta_package.packages), None)
|
||||||
if most_likely:
|
if most_likely:
|
||||||
resolve_package_deps(out, most_likely, only_hard, depth + 1)
|
resolve_package_deps(out, most_likely, only_hard, depth + 1)
|
||||||
|
|
||||||
|
@ -470,9 +467,11 @@ def homepage():
|
||||||
featured = query.filter(Package.tags.any(name="featured")).order_by(
|
featured = query.filter(Package.tags.any(name="featured")).order_by(
|
||||||
func.random()).limit(6).all()
|
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.TOOL).order_by(db.desc(Package.score)).limit(8).all()
|
toplevel_tags = get_toplevel_tags()
|
||||||
pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(8).all()
|
popular = {}
|
||||||
pop_txp = query.filter_by(type=PackageType.ASSETPACK).order_by(db.desc(Package.score)).limit(8).all()
|
# new = join(query.order_by(db.desc(Package.approved_at))).limit(4).all()
|
||||||
|
for toplevel_tag in toplevel_tags:
|
||||||
|
popular[toplevel_tag.name] = query.filter(Package.tags.any(name=toplevel_tag.name)).order_by(db.desc(Package.score)).limit(8).all()
|
||||||
high_reviewed = query.order_by(db.desc(Package.score - Package.score_downloads)) \
|
high_reviewed = query.order_by(db.desc(Package.score - Package.score_downloads)) \
|
||||||
.filter(Package.reviews.any()).limit(4).all()
|
.filter(Package.reviews.any()).limit(4).all()
|
||||||
|
|
||||||
|
@ -487,16 +486,15 @@ def homepage():
|
||||||
|
|
||||||
def mapPackages(packages: List[Package]):
|
def mapPackages(packages: List[Package]):
|
||||||
return [pkg.getAsDictionaryShort(current_app.config["BASE_URL"]) for pkg in packages]
|
return [pkg.getAsDictionaryShort(current_app.config["BASE_URL"]) for pkg in packages]
|
||||||
|
popular = { k:mapPackages(v) for (k, v) in popular.items() }
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"count": count,
|
"count": count,
|
||||||
"downloads": downloads,
|
"downloads": downloads,
|
||||||
"featured": mapPackages(featured),
|
"featured": mapPackages(featured),
|
||||||
"new": mapPackages(new),
|
"new": mapPackages(new),
|
||||||
"updated": mapPackages(updated),
|
"updated": mapPackages(updated),
|
||||||
"pop_mod": mapPackages(pop_mod),
|
"popular": popular,
|
||||||
"pop_txp": mapPackages(pop_txp),
|
"toplevel": [ x.getAsDictionary() for x in toplevel_tags ],
|
||||||
"pop_game": mapPackages(pop_gam),
|
|
||||||
"high_reviewed": mapPackages(high_reviewed)
|
"high_reviewed": mapPackages(high_reviewed)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -505,7 +503,7 @@ def homepage():
|
||||||
@cors_allowed
|
@cors_allowed
|
||||||
def welcome_v1():
|
def welcome_v1():
|
||||||
featured = Package.query \
|
featured = Package.query \
|
||||||
.filter(Package.type == PackageType.GAME, Package.state == PackageState.APPROVED,
|
.filter(Package.state == PackageState.APPROVED,
|
||||||
Package.tags.any(name="featured")) \
|
Package.tags.any(name="featured")) \
|
||||||
.order_by(func.random()) \
|
.order_by(func.random()) \
|
||||||
.limit(5).all()
|
.limit(5).all()
|
||||||
|
@ -520,23 +518,6 @@ def welcome_v1():
|
||||||
"featured": map_packages(featured),
|
"featured": map_packages(featured),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# @bp.route("/api/minetest_versions/")
|
|
||||||
# @cors_allowed
|
|
||||||
# def versions():
|
|
||||||
# protocol_version = request.args.get("protocol_version")
|
|
||||||
# engine_version = request.args.get("engine_version")
|
|
||||||
# if protocol_version or engine_version:
|
|
||||||
# rel = MinetestRelease.get(engine_version, get_int_or_abort(protocol_version))
|
|
||||||
# if rel is None:
|
|
||||||
# error(404, "No releases found")
|
|
||||||
|
|
||||||
# return jsonify(rel.getAsDictionary())
|
|
||||||
|
|
||||||
# return jsonify([rel.getAsDictionary() \
|
|
||||||
# for rel in MinetestRelease.query.all() if rel.getActual() is not None])
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/api/dependencies/")
|
@bp.route("/api/dependencies/")
|
||||||
@cors_allowed
|
@cors_allowed
|
||||||
def all_deps():
|
def all_deps():
|
||||||
|
@ -545,7 +526,7 @@ def all_deps():
|
||||||
|
|
||||||
def format_pkg(pkg: Package):
|
def format_pkg(pkg: Package):
|
||||||
return {
|
return {
|
||||||
"type": pkg.type.toName(),
|
# "type": pkg.type.toName(),
|
||||||
"author": pkg.author.username,
|
"author": pkg.author.username,
|
||||||
"name": pkg.name,
|
"name": pkg.name,
|
||||||
"provides": [x.name for x in pkg.provides],
|
"provides": [x.name for x in pkg.provides],
|
||||||
|
|
|
@ -3,6 +3,7 @@ from flask import Blueprint, render_template, redirect
|
||||||
bp = Blueprint("homepage", __name__)
|
bp = Blueprint("homepage", __name__)
|
||||||
|
|
||||||
from app.models import *
|
from app.models import *
|
||||||
|
from app.utils import get_toplevel_tags
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
|
@ -18,11 +19,12 @@ def home():
|
||||||
count = query.count()
|
count = query.count()
|
||||||
|
|
||||||
featured = query.filter(Package.tags.any(name="featured")).order_by(func.random()).limit(6).all()
|
featured = query.filter(Package.tags.any(name="featured")).order_by(func.random()).limit(6).all()
|
||||||
|
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).all()
|
||||||
|
popular = {}
|
||||||
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.TOOL).order_by(db.desc(Package.score))).limit(8).all()
|
for toplevel_tag in toplevel_tags:
|
||||||
pop_gam = join(query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score))).limit(8).all()
|
popular[toplevel_tag.name] = join(query.filter(Package.tags.any(name=toplevel_tag.name)).order_by(db.desc(Package.score))).limit(8).all()
|
||||||
pop_txp = join(query.filter_by(type=PackageType.ASSETPACK).order_by(db.desc(Package.score))).limit(8).all()
|
|
||||||
high_reviewed = join(query.order_by(db.desc(Package.score - Package.score_downloads))) \
|
high_reviewed = join(query.order_by(db.desc(Package.score - Package.score_downloads))) \
|
||||||
.filter(Package.reviews.any()).limit(4).all()
|
.filter(Package.reviews.any()).limit(4).all()
|
||||||
|
|
||||||
|
@ -41,4 +43,4 @@ def home():
|
||||||
.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, featured=featured,
|
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, popular=popular, toplevel=toplevel_tags, high_reviewed=high_reviewed, reviews=reviews)
|
||||||
|
|
|
@ -18,16 +18,13 @@ from flask import render_template, abort
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from app.utils import is_package_page
|
from app.utils import is_package_page, get_toplevel_tags
|
||||||
from ...models import Package, PackageType, PackageState, db, PackageRelease
|
from ...models import Package, PackageState, db, PackageRelease
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/hub/")
|
@bp.route("/packages/<author>/<name>/hub/")
|
||||||
@is_package_page
|
@is_package_page
|
||||||
def game_hub(package: Package):
|
def game_hub(package: Package):
|
||||||
if package.type != PackageType.GAME:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
def join(query):
|
def join(query):
|
||||||
return query.options(
|
return query.options(
|
||||||
joinedload(Package.license),
|
joinedload(Package.license),
|
||||||
|
@ -37,9 +34,11 @@ def game_hub(package: Package):
|
||||||
count = query.count()
|
count = query.count()
|
||||||
|
|
||||||
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.TOOL).order_by(db.desc(Package.score))).limit(8).all()
|
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).order_by(db.desc(Tag.id)).all()
|
||||||
pop_gam = join(query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score))).limit(8).all()
|
popular = {}
|
||||||
pop_txp = join(query.filter_by(type=PackageType.ASSETPACK).order_by(db.desc(Package.score))).limit(8).all()
|
new = join(query.order_by(db.desc(Package.approved_at))).limit(4).all()
|
||||||
|
for toplevel_tag in toplevel_tags:
|
||||||
|
popular[toplevel_tag.name] = join(query.filter(Package.tags.any(name=toplevel_tag.name)).order_by(db.desc(Package.score))).limit(8).all()
|
||||||
high_reviewed = join(query.order_by(db.desc(Package.score - Package.score_downloads))) \
|
high_reviewed = join(query.order_by(db.desc(Package.score - Package.score_downloads))) \
|
||||||
.filter(Package.reviews.any()).limit(4).all()
|
.filter(Package.reviews.any()).limit(4).all()
|
||||||
|
|
||||||
|
@ -50,5 +49,5 @@ def game_hub(package: Package):
|
||||||
updated = updated[:4]
|
updated = updated[:4]
|
||||||
|
|
||||||
return render_template("packages/game_hub.html", package=package, count=count,
|
return render_template("packages/game_hub.html", package=package, count=count,
|
||||||
new=new, updated=updated, pop_mod=pop_mod, pop_txp=pop_txp, pop_gam=pop_gam,
|
new=new, updated=updated, popular=popular, toplevel=toplevel_tags,
|
||||||
high_reviewed=high_reviewed)
|
high_reviewed=high_reviewed)
|
||||||
|
|
|
@ -99,10 +99,12 @@ def list_all():
|
||||||
|
|
||||||
selected_tags = set(qb.tags)
|
selected_tags = set(qb.tags)
|
||||||
|
|
||||||
|
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).all()
|
||||||
|
|
||||||
return render_template("packages/list.html",
|
return render_template("packages/list.html",
|
||||||
query_hint=title, packages=query.items, pagination=query,
|
query_hint=title, packages=query.items, pagination=query,
|
||||||
query=search, tags=tags, selected_tags=selected_tags, type=type_name,
|
query=search, tags=tags, selected_tags=selected_tags, type=type_name,
|
||||||
authors=authors, packages_count=query.total, topics=topics)
|
authors=authors, packages_count=query.total, topics=topics, toplevel=toplevel_tags)
|
||||||
|
|
||||||
|
|
||||||
def getReleases(package):
|
def getReleases(package):
|
||||||
|
@ -120,7 +122,7 @@ def view(package):
|
||||||
package.checkPerm(current_user, Permission.APPROVE_NEW))
|
package.checkPerm(current_user, Permission.APPROVE_NEW))
|
||||||
|
|
||||||
conflicting_modnames = None
|
conflicting_modnames = None
|
||||||
if show_similar and package.type != PackageType.ASSETPACK:
|
if show_similar:
|
||||||
conflicting_modnames = db.session.query(MetaPackage.name) \
|
conflicting_modnames = db.session.query(MetaPackage.name) \
|
||||||
.filter(MetaPackage.id.in_([ mp.id for mp in package.provides ])) \
|
.filter(MetaPackage.id.in_([ mp.id for mp in package.provides ])) \
|
||||||
.filter(MetaPackage.packages.any(Package.id != package.id)) \
|
.filter(MetaPackage.packages.any(Package.id != package.id)) \
|
||||||
|
@ -136,14 +138,12 @@ def view(package):
|
||||||
conflicting_modnames = set([x[0] for x in conflicting_modnames])
|
conflicting_modnames = set([x[0] for x in conflicting_modnames])
|
||||||
|
|
||||||
packages_uses = None
|
packages_uses = None
|
||||||
if package.type == PackageType.TOOL:
|
packages_uses = Package.query.filter(
|
||||||
packages_uses = Package.query.filter(
|
Package.id != package.id,
|
||||||
Package.type == PackageType.TOOL,
|
Package.state == PackageState.APPROVED,
|
||||||
Package.id != package.id,
|
Package.dependencies.any(
|
||||||
Package.state == PackageState.APPROVED,
|
Dependency.meta_package_id.in_([p.id for p in package.provides]))) \
|
||||||
Package.dependencies.any(
|
.order_by(db.desc(Package.score)).limit(6).all()
|
||||||
Dependency.meta_package_id.in_([p.id for p in package.provides]))) \
|
|
||||||
.order_by(db.desc(Package.score)).limit(6).all()
|
|
||||||
|
|
||||||
releases = getReleases(package)
|
releases = getReleases(package)
|
||||||
|
|
||||||
|
@ -178,11 +178,13 @@ def view(package):
|
||||||
|
|
||||||
has_review = current_user.is_authenticated and PackageReview.query.filter_by(package=package, author=current_user).count() > 0
|
has_review = current_user.is_authenticated and PackageReview.query.filter_by(package=package, author=current_user).count() > 0
|
||||||
|
|
||||||
|
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).all()
|
||||||
|
|
||||||
return render_template("packages/view.html",
|
return render_template("packages/view.html",
|
||||||
package=package, releases=releases, packages_uses=packages_uses,
|
package=package, releases=releases, packages_uses=packages_uses,
|
||||||
conflicting_modnames=conflicting_modnames,
|
conflicting_modnames=conflicting_modnames,
|
||||||
review_thread=review_thread, topic_error=topic_error, topic_error_lvl=topic_error_lvl,
|
review_thread=review_thread, topic_error=topic_error, topic_error_lvl=topic_error_lvl,
|
||||||
threads=threads.all(), has_review=has_review)
|
threads=threads.all(), has_review=has_review, toplevel=toplevel_tags)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/shields/<type>/")
|
@bp.route("/packages/<author>/<name>/shields/<type>/")
|
||||||
|
@ -226,7 +228,14 @@ def makeLabel(obj):
|
||||||
|
|
||||||
|
|
||||||
class PackageForm(FlaskForm):
|
class PackageForm(FlaskForm):
|
||||||
type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.GAME)
|
try:
|
||||||
|
toplevel_tags = [ x.title for x in Tag.query.filter_by(is_toplevel=True).all() ]
|
||||||
|
type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=toplevel_tags, default=toplevel_tags[0])
|
||||||
|
except:
|
||||||
|
toplevel_tags = []
|
||||||
|
# type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=toplevel_tags, default=toplevel_tags[0])
|
||||||
|
|
||||||
|
# type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.GAME)
|
||||||
title = StringField(lazy_gettext("Title (Human-readable)"), [InputRequired(), Length(1, 100)])
|
title = StringField(lazy_gettext("Title (Human-readable)"), [InputRequired(), Length(1, 100)])
|
||||||
name = StringField(lazy_gettext("Name (Technical)"), [InputRequired(), Length(1, 100), Regexp("^[a-zA-Z0-9_\-\.]+$", 0, lazy_gettext("Lower case letters (a-z), digits (0-9), and underscores (_), dashes and periods only"))])
|
name = StringField(lazy_gettext("Name (Technical)"), [InputRequired(), Length(1, 100), Regexp("^[a-zA-Z0-9_\-\.]+$", 0, lazy_gettext("Lower case letters (a-z), digits (0-9), and underscores (_), dashes and periods only"))])
|
||||||
short_desc = StringField(lazy_gettext("Short Description (Plaintext)"), [InputRequired(), Length(1,200)])
|
short_desc = StringField(lazy_gettext("Short Description (Plaintext)"), [InputRequired(), Length(1,200)])
|
||||||
|
@ -293,9 +302,6 @@ def create_edit(author=None, name=None):
|
||||||
form.tags.data = package.tags
|
form.tags.data = package.tags
|
||||||
form.content_warnings.data = package.content_warnings
|
form.content_warnings.data = package.content_warnings
|
||||||
|
|
||||||
if request.method == "POST" and form.type.data == PackageType.ASSETPACK:
|
|
||||||
form.license.data = form.media_license.data
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
wasNew = False
|
wasNew = False
|
||||||
if not package:
|
if not package:
|
||||||
|
@ -353,7 +359,7 @@ def create_edit(author=None, name=None):
|
||||||
form=form, author=author, enable_wizard=enableWizard,
|
form=form, author=author, enable_wizard=enableWizard,
|
||||||
packages=package_query.all(),
|
packages=package_query.all(),
|
||||||
mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all(),
|
mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all(),
|
||||||
tabs=get_package_tabs(current_user, package), current_tab="edit")
|
tabs=get_package_tabs(current_user, package), current_tab="edit", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/state/", methods=["POST"])
|
@bp.route("/packages/<author>/<name>/state/", methods=["POST"])
|
||||||
|
@ -408,7 +414,7 @@ def move_to_state(package):
|
||||||
def remove(package):
|
def remove(package):
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render_template("packages/remove.html", package=package,
|
return render_template("packages/remove.html", package=package,
|
||||||
tabs=get_package_tabs(current_user, package), current_tab="remove")
|
tabs=get_package_tabs(current_user, package), current_tab="remove", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
reason = request.form.get("reason") or "?"
|
reason = request.form.get("reason") or "?"
|
||||||
|
|
||||||
|
@ -501,7 +507,7 @@ def edit_maintainers(package):
|
||||||
users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).order_by(db.asc(User.username)).all()
|
users = User.query.filter(User.rank >= UserRank.NEW_MEMBER).order_by(db.asc(User.username)).all()
|
||||||
|
|
||||||
return render_template("packages/edit_maintainers.html", package=package, form=form,
|
return render_template("packages/edit_maintainers.html", package=package, form=form,
|
||||||
users=users, tabs=get_package_tabs(current_user, package), current_tab="maintainers")
|
users=users, tabs=get_package_tabs(current_user, package), current_tab="maintainers", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/remove-self-maintainer/", methods=["POST"])
|
@bp.route("/packages/<author>/<name>/remove-self-maintainer/", methods=["POST"])
|
||||||
|
@ -540,7 +546,7 @@ def audit(package):
|
||||||
|
|
||||||
pagination = query.paginate(page, num, True)
|
pagination = query.paginate(page, num, True)
|
||||||
return render_template("packages/audit.html", log=pagination.items, pagination=pagination,
|
return render_template("packages/audit.html", log=pagination.items, pagination=pagination,
|
||||||
package=package, tabs=get_package_tabs(current_user, package), current_tab="audit")
|
package=package, tabs=get_package_tabs(current_user, package), current_tab="audit", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
class PackageAliasForm(FlaskForm):
|
class PackageAliasForm(FlaskForm):
|
||||||
|
@ -554,7 +560,7 @@ class PackageAliasForm(FlaskForm):
|
||||||
@rank_required(UserRank.EDITOR)
|
@rank_required(UserRank.EDITOR)
|
||||||
@is_package_page
|
@is_package_page
|
||||||
def alias_list(package: Package):
|
def alias_list(package: Package):
|
||||||
return render_template("packages/alias_list.html", package=package)
|
return render_template("packages/alias_list.html", package=package, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/aliases/new/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/aliases/new/", methods=["GET", "POST"])
|
||||||
|
@ -580,7 +586,7 @@ def alias_create_edit(package: Package, alias_id: int = None):
|
||||||
|
|
||||||
return redirect(package.getURL("packages.alias_list"))
|
return redirect(package.getURL("packages.alias_list"))
|
||||||
|
|
||||||
return render_template("packages/alias_create_edit.html", package=package, form=form)
|
return render_template("packages/alias_create_edit.html", package=package, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/share/")
|
@bp.route("/packages/<author>/<name>/share/")
|
||||||
|
@ -588,7 +594,7 @@ def alias_create_edit(package: Package, alias_id: int = None):
|
||||||
@is_package_page
|
@is_package_page
|
||||||
def share(package):
|
def share(package):
|
||||||
return render_template("packages/share.html", package=package,
|
return render_template("packages/share.html", package=package,
|
||||||
tabs=get_package_tabs(current_user, package), current_tab="share")
|
tabs=get_package_tabs(current_user, package), current_tab="share", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/similar/")
|
@bp.route("/packages/<author>/<name>/similar/")
|
||||||
|
@ -610,4 +616,4 @@ def similar(package):
|
||||||
# .all()
|
# .all()
|
||||||
|
|
||||||
return render_template("packages/similar.html", package=package,
|
return render_template("packages/similar.html", package=package,
|
||||||
packages_modnames=packages_modnames, similar_topics=[])
|
packages_modnames=packages_modnames, similar_topics=[], toplevel=get_toplevel_tags())
|
||||||
|
|
|
@ -35,7 +35,7 @@ from . import bp, get_package_tabs
|
||||||
def list_releases(package):
|
def list_releases(package):
|
||||||
return render_template("packages/releases_list.html",
|
return render_template("packages/releases_list.html",
|
||||||
package=package,
|
package=package,
|
||||||
tabs=get_package_tabs(current_user, package), current_tab="releases")
|
tabs=get_package_tabs(current_user, package), current_tab="releases", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
# def get_mt_releases(is_max):
|
# def get_mt_releases(is_max):
|
||||||
|
@ -92,7 +92,7 @@ def create_release(package):
|
||||||
except LogicError as e:
|
except LogicError as e:
|
||||||
flash(e.message, "danger")
|
flash(e.message, "danger")
|
||||||
|
|
||||||
return render_template("packages/release_new.html", package=package, form=form)
|
return render_template("packages/release_new.html", package=package, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/releases/<id>/download/")
|
@bp.route("/packages/<author>/<name>/releases/<id>/download/")
|
||||||
|
@ -163,7 +163,7 @@ def edit_release(package, id):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(package.getURL("packages.list_releases"))
|
return redirect(package.getURL("packages.list_releases"))
|
||||||
|
|
||||||
return render_template("packages/release_edit.html", package=package, release=release, form=form)
|
return render_template("packages/release_edit.html", package=package, release=release, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ def bulk_change_release(package):
|
||||||
|
|
||||||
return redirect(package.getURL("packages.list_releases"))
|
return redirect(package.getURL("packages.list_releases"))
|
||||||
|
|
||||||
return render_template("packages/release_bulk_change.html", package=package, form=form)
|
return render_template("packages/release_bulk_change.html", package=package, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/releases/<id>/delete/", methods=["POST"])
|
@bp.route("/packages/<author>/<name>/releases/<id>/delete/", methods=["POST"])
|
||||||
|
@ -301,7 +301,7 @@ def update_config(package):
|
||||||
|
|
||||||
return redirect(package.getURL("packages.list_releases"))
|
return redirect(package.getURL("packages.list_releases"))
|
||||||
|
|
||||||
return render_template("packages/update_config.html", package=package, form=form)
|
return render_template("packages/update_config.html", package=package, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/setup-releases/")
|
@bp.route("/packages/<author>/<name>/setup-releases/")
|
||||||
|
@ -314,7 +314,7 @@ def setup_releases(package):
|
||||||
if package.update_config:
|
if package.update_config:
|
||||||
return redirect(package.getURL("packages.update_config"))
|
return redirect(package.getURL("packages.update_config"))
|
||||||
|
|
||||||
return render_template("packages/release_wizard.html", package=package)
|
return render_template("packages/release_wizard.html", package=package, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user/update-configs/")
|
@bp.route("/user/update-configs/")
|
||||||
|
@ -343,4 +343,4 @@ def bulk_update_config(username=None):
|
||||||
Package.update_config.has()) \
|
Package.update_config.has()) \
|
||||||
.order_by(db.asc(Package.title)).all()
|
.order_by(db.asc(Package.title)).all()
|
||||||
|
|
||||||
return render_template("packages/bulk_update_conf.html", user=user, confs=confs, form=form)
|
return render_template("packages/bulk_update_conf.html", user=user, confs=confs, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
|
@ -26,7 +26,7 @@ from wtforms import *
|
||||||
from wtforms.validators import *
|
from wtforms.validators import *
|
||||||
from app.models import db, PackageReview, Thread, ThreadReply, NotificationType, PackageReviewVote, Package, UserRank, \
|
from app.models import db, PackageReview, Thread, ThreadReply, NotificationType, PackageReviewVote, Package, UserRank, \
|
||||||
Permission, AuditSeverity
|
Permission, AuditSeverity
|
||||||
from app.utils import is_package_page, addNotification, get_int_or_abort, isYes, is_safe_url, rank_required, addAuditLog
|
from app.utils import is_package_page, addNotification, get_int_or_abort, isYes, is_safe_url, rank_required, addAuditLog, get_toplevel_tags
|
||||||
from app.tasks.webhooktasks import post_discord_webhook
|
from app.tasks.webhooktasks import post_discord_webhook
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def list_reviews():
|
||||||
num = min(40, get_int_or_abort(request.args.get("n"), 100))
|
num = min(40, get_int_or_abort(request.args.get("n"), 100))
|
||||||
|
|
||||||
pagination = PackageReview.query.order_by(db.desc(PackageReview.created_at)).paginate(page, num, True)
|
pagination = PackageReview.query.order_by(db.desc(PackageReview.created_at)).paginate(page, num, True)
|
||||||
return render_template("packages/reviews_list.html", pagination=pagination, reviews=pagination.items)
|
return render_template("packages/reviews_list.html", pagination=pagination, reviews=pagination.items, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
class ReviewForm(FlaskForm):
|
class ReviewForm(FlaskForm):
|
||||||
|
@ -123,7 +123,7 @@ def review(package):
|
||||||
return redirect(package.getURL("packages.view"))
|
return redirect(package.getURL("packages.view"))
|
||||||
|
|
||||||
return render_template("packages/review_create_edit.html",
|
return render_template("packages/review_create_edit.html",
|
||||||
form=form, package=package, review=review)
|
form=form, package=package, review=review, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/reviews/<reviewer>/delete/", methods=["POST"])
|
@bp.route("/packages/<author>/<name>/reviews/<reviewer>/delete/", methods=["POST"])
|
||||||
|
@ -237,4 +237,4 @@ def review_votes(package):
|
||||||
user_biases_info.sort(key=lambda x: -abs(x.balance))
|
user_biases_info.sort(key=lambda x: -abs(x.balance))
|
||||||
|
|
||||||
return render_template("packages/review_votes.html", form=form, package=package, reviews=package.reviews,
|
return render_template("packages/review_votes.html", form=form, package=package, reviews=package.reviews,
|
||||||
user_biases=user_biases_info)
|
user_biases=user_biases_info, toplevel=get_toplevel_tags())
|
||||||
|
|
|
@ -73,7 +73,7 @@ def screenshots(package):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return render_template("packages/screenshots.html", package=package, form=form,
|
return render_template("packages/screenshots.html", package=package, form=form,
|
||||||
tabs=get_package_tabs(current_user, package), current_tab="screenshots")
|
tabs=get_package_tabs(current_user, package), current_tab="screenshots", toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/screenshots/new/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/screenshots/new/", methods=["GET", "POST"])
|
||||||
|
@ -92,7 +92,7 @@ def create_screenshot(package):
|
||||||
except LogicError as e:
|
except LogicError as e:
|
||||||
flash(e.message, "danger")
|
flash(e.message, "danger")
|
||||||
|
|
||||||
return render_template("packages/screenshot_new.html", package=package, form=form)
|
return render_template("packages/screenshot_new.html", package=package, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/screenshots/<id>/edit/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/screenshots/<id>/edit/", methods=["GET", "POST"])
|
||||||
|
@ -124,7 +124,7 @@ def edit_screenshot(package, id):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(package.getURL("packages.screenshots"))
|
return redirect(package.getURL("packages.screenshots"))
|
||||||
|
|
||||||
return render_template("packages/screenshot_edit.html", package=package, screenshot=screenshot, form=form)
|
return render_template("packages/screenshot_edit.html", package=package, screenshot=screenshot, form=form, toplevel=get_toplevel_tags())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/<author>/<name>/screenshots/<id>/delete/", methods=["POST"])
|
@bp.route("/packages/<author>/<name>/screenshots/<id>/delete/", methods=["POST"])
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import string
|
||||||
|
import random
|
||||||
from flask import *
|
from flask import *
|
||||||
from flask_babel import gettext, lazy_gettext, get_locale
|
from flask_babel import gettext, lazy_gettext, get_locale
|
||||||
from flask_login import current_user, login_required, logout_user, login_user
|
from flask_login import current_user, login_required, logout_user, login_user
|
||||||
|
@ -23,6 +25,7 @@ from flask_wtf import FlaskForm
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from wtforms import *
|
from wtforms import *
|
||||||
from wtforms.validators import *
|
from wtforms.validators import *
|
||||||
|
from captcha.image import ImageCaptcha
|
||||||
|
|
||||||
from app.models import *
|
from app.models import *
|
||||||
from app.tasks.emails import send_verify_email, send_anon_email, send_unsubscribe_verify, send_user_email
|
from app.tasks.emails import send_verify_email, send_anon_email, send_unsubscribe_verify, send_user_email
|
||||||
|
@ -105,13 +108,13 @@ class RegisterForm(FlaskForm):
|
||||||
Regexp("^[a-zA-Z0-9._-]+$", message=lazy_gettext("Only a-zA-Z0-9._ allowed"))])
|
Regexp("^[a-zA-Z0-9._-]+$", message=lazy_gettext("Only a-zA-Z0-9._ allowed"))])
|
||||||
email = StringField(lazy_gettext("Email"), [InputRequired(), Email()])
|
email = StringField(lazy_gettext("Email"), [InputRequired(), Email()])
|
||||||
password = PasswordField(lazy_gettext("Password"), [InputRequired(), Length(6, 100)])
|
password = PasswordField(lazy_gettext("Password"), [InputRequired(), Length(6, 100)])
|
||||||
question = StringField(lazy_gettext("What is the result of the above calculation?"), [InputRequired()])
|
question = StringField(lazy_gettext("Type the characters from the image above."), [InputRequired()])
|
||||||
agree = BooleanField(lazy_gettext("I agree"), [DataRequired()])
|
agree = BooleanField(lazy_gettext("I agree"), [DataRequired()])
|
||||||
submit = SubmitField(lazy_gettext("Register"))
|
submit = SubmitField(lazy_gettext("Register"))
|
||||||
|
|
||||||
|
|
||||||
def handle_register(form):
|
def handle_register(form):
|
||||||
if form.question.data.strip().lower() != "19":
|
if form.question.data.strip().lower() != session["captcha-solution"]:
|
||||||
flash(gettext("Incorrect captcha answer"), "danger")
|
flash(gettext("Incorrect captcha answer"), "danger")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -181,7 +184,13 @@ def register():
|
||||||
if ret:
|
if ret:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
return render_template("users/register.html", form=form,
|
image = ImageCaptcha()
|
||||||
|
characters = string.ascii_letters + string.digits
|
||||||
|
solution = ''.join(random.choice(characters) for i in range(4))
|
||||||
|
data = image.generate(solution)
|
||||||
|
session["captcha-solution"] = solution
|
||||||
|
b64data = "data:image/png;base64," + base64.b64encode(data.read()).decode("utf-8")
|
||||||
|
return render_template("users/register.html", form=form, captcha=b64data,
|
||||||
suggested_password=genphrase(entropy=52, wordset="bip39"))
|
suggested_password=genphrase(entropy=52, wordset="bip39"))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -151,24 +151,17 @@ def get_user_medals(user: User) -> Tuple[List[Medal], List[Medal]]:
|
||||||
.filter(text("rank <= 30")) \
|
.filter(text("rank <= 30")) \
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
user_package_ranks = next(
|
user_package_ranks = next((x for x in user_package_ranks if x[2] <= 10), None)
|
||||||
(x for x in user_package_ranks if x[0] == PackageType.TOOL or x[2] <= 10),
|
|
||||||
None)
|
|
||||||
if user_package_ranks:
|
if user_package_ranks:
|
||||||
top_rank = user_package_ranks[2]
|
top_rank = user_package_ranks[2]
|
||||||
top_type = PackageType.coerce(user_package_ranks[0])
|
# top_type = PackageType.coerce(user_package_ranks[0])
|
||||||
if top_rank == 1:
|
if top_rank == 1:
|
||||||
title = gettext(u"Top %(type)s", type=top_type.text.lower())
|
title = gettext(u"Top projects", type=top_type.text.lower())
|
||||||
else:
|
else:
|
||||||
title = gettext(u"Top %(group)d %(type)s", group=top_rank, type=top_type.text.lower())
|
title = gettext(u"Top %(group)d projects", group=top_rank, type=top_type.text.lower())
|
||||||
if top_type == PackageType.TOOL:
|
icon = "fa-paint-brush"
|
||||||
icon = "fa-box"
|
|
||||||
elif top_type == PackageType.GAME:
|
|
||||||
icon = "fa-gamepad"
|
|
||||||
else:
|
|
||||||
icon = "fa-paint-brush"
|
|
||||||
|
|
||||||
description = gettext(u"%(display_name)s has a %(type)s placed at #%(place)d.",
|
description = gettext(u"%(display_name)s has a projects placed at #%(place)d.",
|
||||||
display_name=user.display_name, type=top_type.text.lower(), place=top_rank)
|
display_name=user.display_name, type=top_type.text.lower(), place=top_rank)
|
||||||
unlocked.append(
|
unlocked.append(
|
||||||
Medal.make_unlocked(place_to_color(top_rank), icon, title, description))
|
Medal.make_unlocked(place_to_color(top_rank), icon, title, description))
|
||||||
|
|
|
@ -36,11 +36,11 @@ def populate(session):
|
||||||
featured.is_protected = True
|
featured.is_protected = True
|
||||||
|
|
||||||
# These tags replace "package types"
|
# These tags replace "package types"
|
||||||
game_tag = Tag("Game")
|
game_tag = Tag("Games")
|
||||||
game_tag.is_toplevel = True
|
game_tag.is_toplevel = True
|
||||||
tool_tag = Tag("Tool")
|
tool_tag = Tag("Tools")
|
||||||
tool_tag.is_toplevel = True
|
tool_tag.is_toplevel = True
|
||||||
mod_tag = Tag("Mod")
|
mod_tag = Tag("Mods")
|
||||||
mod_tag.is_toplevel = True
|
mod_tag.is_toplevel = True
|
||||||
session.add(featured)
|
session.add(featured)
|
||||||
session.add(game_tag)
|
session.add(game_tag)
|
||||||
|
@ -110,7 +110,7 @@ def populate_test_data(session):
|
||||||
tool.title = "Alpha Test"
|
tool.title = "Alpha Test"
|
||||||
tool.license = licenses["MIT"]
|
tool.license = licenses["MIT"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = admin_user
|
tool.author = admin_user
|
||||||
tool.tags.append(tags["mapgen"])
|
tool.tags.append(tags["mapgen"])
|
||||||
tool.tags.append(tags["environment"])
|
tool.tags.append(tags["environment"])
|
||||||
|
@ -134,7 +134,7 @@ def populate_test_data(session):
|
||||||
mod1.title = "Awards"
|
mod1.title = "Awards"
|
||||||
mod1.license = licenses["LGPLv2.1"]
|
mod1.license = licenses["LGPLv2.1"]
|
||||||
mod1.media_license = licenses["MIT"]
|
mod1.media_license = licenses["MIT"]
|
||||||
mod1.type = PackageType.TOOL
|
# mod1.type = PackageType.TOOL
|
||||||
mod1.author = admin_user
|
mod1.author = admin_user
|
||||||
mod1.tags.append(tags["player_effects"])
|
mod1.tags.append(tags["player_effects"])
|
||||||
mod1.repo = "https://github.com/libregaming/awards"
|
mod1.repo = "https://github.com/libregaming/awards"
|
||||||
|
@ -170,7 +170,7 @@ awards.register_achievement("award_mesefind",{
|
||||||
mod2.name = "mesecons"
|
mod2.name = "mesecons"
|
||||||
mod2.title = "Mesecons"
|
mod2.title = "Mesecons"
|
||||||
mod2.tags.append(tags["tools"])
|
mod2.tags.append(tags["tools"])
|
||||||
mod2.type = PackageType.TOOL
|
# mod2.type = PackageType.TOOL
|
||||||
mod2.license = licenses["LGPLv3"]
|
mod2.license = licenses["LGPLv3"]
|
||||||
mod2.media_license = licenses["MIT"]
|
mod2.media_license = licenses["MIT"]
|
||||||
mod2.author = jeija
|
mod2.author = jeija
|
||||||
|
@ -260,7 +260,7 @@ No warranty is provided, express or implied, for any part of the project.
|
||||||
tool.title = "Handholds"
|
tool.title = "Handholds"
|
||||||
tool.license = licenses["MIT"]
|
tool.license = licenses["MIT"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = ez
|
tool.author = ez
|
||||||
tool.tags.append(tags["player_effects"])
|
tool.tags.append(tags["player_effects"])
|
||||||
tool.repo = "https://github.com/ezhh/handholds"
|
tool.repo = "https://github.com/ezhh/handholds"
|
||||||
|
@ -284,7 +284,7 @@ No warranty is provided, express or implied, for any part of the project.
|
||||||
tool.title = "Other Worlds"
|
tool.title = "Other Worlds"
|
||||||
tool.license = licenses["MIT"]
|
tool.license = licenses["MIT"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = ez
|
tool.author = ez
|
||||||
tool.tags.append(tags["mapgen"])
|
tool.tags.append(tags["mapgen"])
|
||||||
tool.tags.append(tags["environment"])
|
tool.tags.append(tags["environment"])
|
||||||
|
@ -301,7 +301,7 @@ No warranty is provided, express or implied, for any part of the project.
|
||||||
tool.title = "Food"
|
tool.title = "Food"
|
||||||
tool.license = licenses["LGPLv2.1"]
|
tool.license = licenses["LGPLv2.1"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = admin_user
|
tool.author = admin_user
|
||||||
tool.tags.append(tags["player_effects"])
|
tool.tags.append(tags["player_effects"])
|
||||||
tool.repo = "https://github.com/libregaming/food/"
|
tool.repo = "https://github.com/libregaming/food/"
|
||||||
|
@ -317,7 +317,7 @@ No warranty is provided, express or implied, for any part of the project.
|
||||||
tool.title = "Sweet Foods"
|
tool.title = "Sweet Foods"
|
||||||
tool.license = licenses["CC0"]
|
tool.license = licenses["CC0"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = admin_user
|
tool.author = admin_user
|
||||||
tool.tags.append(tags["player_effects"])
|
tool.tags.append(tags["player_effects"])
|
||||||
tool.repo = "https://github.com/libregaming/food_sweet/"
|
tool.repo = "https://github.com/libregaming/food_sweet/"
|
||||||
|
@ -332,7 +332,7 @@ No warranty is provided, express or implied, for any part of the project.
|
||||||
game1.state = PackageState.APPROVED
|
game1.state = PackageState.APPROVED
|
||||||
game1.name = "capturetheflag"
|
game1.name = "capturetheflag"
|
||||||
game1.title = "Capture The Flag"
|
game1.title = "Capture The Flag"
|
||||||
game1.type = PackageType.GAME
|
# game1.type = PackageType.GAME
|
||||||
game1.license = licenses["LGPLv2.1"]
|
game1.license = licenses["LGPLv2.1"]
|
||||||
game1.media_license = licenses["MIT"]
|
game1.media_license = licenses["MIT"]
|
||||||
game1.author = admin_user
|
game1.author = admin_user
|
||||||
|
@ -397,7 +397,7 @@ Uses the CTF PvP Engine.
|
||||||
tool.title = "PixelBOX Reloaded"
|
tool.title = "PixelBOX Reloaded"
|
||||||
tool.license = licenses["CC0"]
|
tool.license = licenses["CC0"]
|
||||||
tool.media_license = licenses["MIT"]
|
tool.media_license = licenses["MIT"]
|
||||||
tool.type = PackageType.ASSETPACK
|
# tool.type = PackageType.ASSETPACK
|
||||||
tool.author = admin_user
|
tool.author = admin_user
|
||||||
tool.forums = 14132
|
tool.forums = 14132
|
||||||
tool.short_desc = "This is an update of the original PixelBOX texture pack by the brillant artist Gambit"
|
tool.short_desc = "This is an update of the original PixelBOX texture pack by the brillant artist Gambit"
|
||||||
|
@ -413,16 +413,16 @@ Uses the CTF PvP Engine.
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
metas = {}
|
# metas = {}
|
||||||
for package in Package.query.filter_by(type=PackageType.TOOL).all():
|
# for package in Package.query.filter_by(type=PackageType.TOOL).all():
|
||||||
meta = None
|
# meta = None
|
||||||
try:
|
# try:
|
||||||
meta = metas[package.name]
|
# meta = metas[package.name]
|
||||||
except KeyError:
|
# except KeyError:
|
||||||
meta = MetaPackage(package.name)
|
# meta = MetaPackage(package.name)
|
||||||
session.add(meta)
|
# session.add(meta)
|
||||||
metas[package.name] = meta
|
# metas[package.name] = meta
|
||||||
package.provides.append(meta)
|
# package.provides.append(meta)
|
||||||
|
|
||||||
dep = Dependency(food_sweet, meta=metas["food"])
|
# dep = Dependency(food_sweet, meta=metas["food"])
|
||||||
session.add(dep)
|
# session.add(dep)
|
||||||
|
|
|
@ -390,9 +390,8 @@ Supported query parameters:
|
||||||
* `downloads`: get number of downloads
|
* `downloads`: get number of downloads
|
||||||
* `new`: new packages
|
* `new`: new packages
|
||||||
* `updated`: recently updated packages
|
* `updated`: recently updated packages
|
||||||
* `pop_mod`: popular mods
|
* `popular`: popular packages
|
||||||
* `pop_txp`: popular textures
|
* `toplevel`: toplevel nav tags
|
||||||
* `pop_game`: popular games
|
|
||||||
* `high_reviewed`: highest reviewed
|
* `high_reviewed`: highest reviewed
|
||||||
* GET `/api/welcome/v1/` ([View](/api/welcome/v1/)) - in-menu welcome dialog. Experimental (may change without warning)
|
* GET `/api/welcome/v1/` ([View](/api/welcome/v1/)) - in-menu welcome dialog. Experimental (may change without warning)
|
||||||
* `featured`: featured games
|
* `featured`: featured games
|
||||||
|
|
|
@ -13,7 +13,7 @@ If you don't, then you can just sign up using an email address and password.
|
||||||
|
|
||||||
GitHub can only be used to login, not to register.
|
GitHub can only be used to login, not to register.
|
||||||
|
|
||||||
<a class="btn btn-primary" href="/user/claim/">Register</a>
|
<a class="btn btn-primary" href="/user/register/">Register</a>
|
||||||
|
|
||||||
|
|
||||||
### My verification email never arrived
|
### My verification email never arrived
|
||||||
|
|
|
@ -20,7 +20,7 @@ import sys
|
||||||
from typing import List, Dict, Optional, Iterator, Iterable
|
from typing import List, Dict, Optional, Iterator, Iterable
|
||||||
|
|
||||||
from app.logic.LogicError import LogicError
|
from app.logic.LogicError import LogicError
|
||||||
from app.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport, db
|
from app.models import Package, MetaPackage, PackageState, PackageGameSupport, db
|
||||||
|
|
||||||
"""
|
"""
|
||||||
get_game_support(package):
|
get_game_support(package):
|
||||||
|
@ -128,39 +128,36 @@ class GameSupportResolver:
|
||||||
history = history.copy()
|
history = history.copy()
|
||||||
history.append(key)
|
history.append(key)
|
||||||
|
|
||||||
if package.type == PackageType.GAME:
|
# if package.type == PackageType.GAME:
|
||||||
return PackageSet([package])
|
return PackageSet([package])
|
||||||
|
|
||||||
if key in self.resolved_packages:
|
# if key in self.resolved_packages:
|
||||||
return self.resolved_packages.get(key)
|
# return self.resolved_packages.get(key)
|
||||||
|
|
||||||
if key in self.checked_packages:
|
# if key in self.checked_packages:
|
||||||
print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
# print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
||||||
return PackageSet()
|
# return PackageSet()
|
||||||
|
|
||||||
self.checked_packages.add(key)
|
# self.checked_packages.add(key)
|
||||||
|
|
||||||
if package.type != PackageType.TOOL:
|
# retval = PackageSet()
|
||||||
raise LogicError(500, "Got non-tool")
|
|
||||||
|
|
||||||
retval = PackageSet()
|
# for dep in package.dependencies.filter_by(optional=False).all():
|
||||||
|
# ret = self.resolve_for_meta_package(dep.meta_package, history)
|
||||||
|
# if len(ret) == 0:
|
||||||
|
# continue
|
||||||
|
# elif len(retval) == 0:
|
||||||
|
# retval.update(ret)
|
||||||
|
# else:
|
||||||
|
# retval.intersection_update(ret)
|
||||||
|
# if len(retval) == 0:
|
||||||
|
# raise LogicError(500, f"Detected game support contradiction, {key} may not be compatible with any games")
|
||||||
|
|
||||||
for dep in package.dependencies.filter_by(optional=False).all():
|
# self.resolved_packages[key] = retval
|
||||||
ret = self.resolve_for_meta_package(dep.meta_package, history)
|
# return retval
|
||||||
if len(ret) == 0:
|
|
||||||
continue
|
|
||||||
elif len(retval) == 0:
|
|
||||||
retval.update(ret)
|
|
||||||
else:
|
|
||||||
retval.intersection_update(ret)
|
|
||||||
if len(retval) == 0:
|
|
||||||
raise LogicError(500, f"Detected game support contradiction, {key} may not be compatible with any games")
|
|
||||||
|
|
||||||
self.resolved_packages[key] = retval
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def update_all(self) -> None:
|
def update_all(self) -> None:
|
||||||
for package in Package.query.filter(Package.type == PackageType.TOOL, Package.state != PackageState.DELETED).all():
|
for package in Package.query.filter(Package.state != PackageState.DELETED).all():
|
||||||
retval = self.resolve(package, [])
|
retval = self.resolve(package, [])
|
||||||
for game in retval:
|
for game in retval:
|
||||||
support = PackageGameSupport(package, game)
|
support = PackageGameSupport(package, game)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import validators
|
||||||
from flask_babel import lazy_gettext
|
from flask_babel import lazy_gettext
|
||||||
|
|
||||||
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, \
|
from app.models import User, Package, MetaPackage, Tag, ContentWarning, db, Permission, AuditSeverity, \
|
||||||
License, UserRank, PackageDevState
|
License, UserRank, PackageDevState
|
||||||
from app.utils import addAuditLog
|
from app.utils import addAuditLog
|
||||||
from app.utils.url import clean_youtube_url
|
from app.utils.url import clean_youtube_url
|
||||||
|
@ -118,8 +118,8 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
|
||||||
|
|
||||||
validate(data)
|
validate(data)
|
||||||
|
|
||||||
if "type" in data:
|
# if "type" in data:
|
||||||
data["type"] = PackageType.coerce(data["type"])
|
# data["type"] = PackageType.coerce(data["type"])
|
||||||
|
|
||||||
if "dev_state" in data:
|
if "dev_state" in data:
|
||||||
data["dev_state"] = PackageDevState.coerce(data["dev_state"])
|
data["dev_state"] = PackageDevState.coerce(data["dev_state"])
|
||||||
|
@ -140,12 +140,12 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
|
||||||
if key in data:
|
if key in data:
|
||||||
setattr(package, key, data[key])
|
setattr(package, key, data[key])
|
||||||
|
|
||||||
if package.type == PackageType.ASSETPACK:
|
# if package.type == PackageType.ASSETPACK:
|
||||||
package.license = package.media_license
|
# package.license = package.media_license
|
||||||
|
|
||||||
if was_new and package.type == PackageType.TOOL:
|
# if was_new and package.type == PackageType.TOOL:
|
||||||
m = MetaPackage.GetOrCreate(package.name, {})
|
# m = MetaPackage.GetOrCreate(package.name, {})
|
||||||
package.provides.append(m)
|
# package.provides.append(m)
|
||||||
|
|
||||||
if "tags" in data:
|
if "tags" in data:
|
||||||
old_tags = list(package.tags)
|
old_tags = list(package.tags)
|
||||||
|
|
|
@ -120,7 +120,7 @@ class ForumTopic(db.Model):
|
||||||
wip = db.Column(db.Boolean, default=False, nullable=False)
|
wip = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
discarded = db.Column(db.Boolean, default=False, nullable=False)
|
discarded = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
type = db.Column(db.Enum(PackageType), nullable=False)
|
# type = db.Column(db.Enum(PackageType), nullable=False)
|
||||||
title = db.Column(db.String(200), nullable=False)
|
title = db.Column(db.String(200), nullable=False)
|
||||||
name = db.Column(db.String(30), nullable=True)
|
name = db.Column(db.String(30), nullable=True)
|
||||||
link = db.Column(db.String(200), nullable=True)
|
link = db.Column(db.String(200), nullable=True)
|
||||||
|
|
|
@ -48,49 +48,49 @@ class License(db.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class PackageType(enum.Enum):
|
# class PackageType(enum.Enum):
|
||||||
GAME = "Game"
|
# GAME = "Game"
|
||||||
TOOL = "Tool"
|
# TOOL = "Tool"
|
||||||
ASSETPACK = "Asset Pack"
|
# ASSETPACK = "Asset Pack"
|
||||||
|
|
||||||
def toName(self):
|
# def toName(self):
|
||||||
return self.name.lower()
|
# return self.name.lower()
|
||||||
|
|
||||||
def __str__(self):
|
# def __str__(self):
|
||||||
return self.name
|
# return self.name
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def text(self):
|
# def text(self):
|
||||||
if self == PackageType.TOOL:
|
# if self == PackageType.TOOL:
|
||||||
return lazy_gettext("Tool")
|
# return lazy_gettext("Tool")
|
||||||
elif self == PackageType.GAME:
|
# elif self == PackageType.GAME:
|
||||||
return lazy_gettext("Game")
|
# return lazy_gettext("Game")
|
||||||
elif self == PackageType.ASSETPACK:
|
# elif self == PackageType.ASSETPACK:
|
||||||
return lazy_gettext("Asset Pack")
|
# return lazy_gettext("Asset Pack")
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def plural(self):
|
# def plural(self):
|
||||||
if self == PackageType.TOOL:
|
# if self == PackageType.TOOL:
|
||||||
return lazy_gettext("Tools")
|
# return lazy_gettext("Tools")
|
||||||
elif self == PackageType.GAME:
|
# elif self == PackageType.GAME:
|
||||||
return lazy_gettext("Games")
|
# return lazy_gettext("Games")
|
||||||
elif self == PackageType.ASSETPACK:
|
# elif self == PackageType.ASSETPACK:
|
||||||
return lazy_gettext("Asset Packs")
|
# return lazy_gettext("Asset Packs")
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def get(cls, name):
|
# def get(cls, name):
|
||||||
try:
|
# try:
|
||||||
return PackageType[name.upper()]
|
# return PackageType[name.upper()]
|
||||||
except KeyError:
|
# except KeyError:
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def choices(cls):
|
# def choices(cls):
|
||||||
return [(choice, choice.text) for choice in cls]
|
# return [(choice, choice.text) for choice in cls]
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def coerce(cls, item):
|
# def coerce(cls, item):
|
||||||
return item if type(item) == PackageType else PackageType[item.upper()]
|
# return item if type(item) == PackageType else PackageType[item.upper()]
|
||||||
|
|
||||||
|
|
||||||
class PackageDevState(enum.Enum):
|
class PackageDevState(enum.Enum):
|
||||||
|
@ -218,7 +218,7 @@ class PackagePropertyKey(enum.Enum):
|
||||||
title = "Title"
|
title = "Title"
|
||||||
short_desc = "Short Description"
|
short_desc = "Short Description"
|
||||||
desc = "Description"
|
desc = "Description"
|
||||||
type = "Type"
|
# type = "Type"
|
||||||
license = "License"
|
license = "License"
|
||||||
media_license = "Media License"
|
media_license = "Media License"
|
||||||
tags = "Tags"
|
tags = "Tags"
|
||||||
|
@ -378,7 +378,7 @@ class Package(db.Model):
|
||||||
desc = db.Column(db.UnicodeText, nullable=True)
|
desc = db.Column(db.UnicodeText, nullable=True)
|
||||||
build_desc = db.Column(db.UnicodeText, nullable=True)
|
build_desc = db.Column(db.UnicodeText, nullable=True)
|
||||||
install_desc = db.Column(db.UnicodeText, nullable=True)
|
install_desc = db.Column(db.UnicodeText, nullable=True)
|
||||||
type = db.Column(db.Enum(PackageType), nullable=False)
|
# type = db.Column(db.Enum(PackageType), nullable=False)
|
||||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||||
approved_at = db.Column(db.DateTime, nullable=True, default=None)
|
approved_at = db.Column(db.DateTime, nullable=True, default=None)
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ class Package(db.Model):
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"author": self.author.username,
|
"author": self.author.username,
|
||||||
"short_description": short_desc,
|
"short_description": short_desc,
|
||||||
"type": self.type.toName(),
|
# "type": self.type.toName(),
|
||||||
"release": release_id,
|
"release": release_id,
|
||||||
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
|
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
|
||||||
"aliases": [ alias.getAsDictionary() for alias in self.aliases ],
|
"aliases": [ alias.getAsDictionary() for alias in self.aliases ],
|
||||||
|
@ -559,7 +559,7 @@ class Package(db.Model):
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"short_description": self.short_desc,
|
"short_description": self.short_desc,
|
||||||
"long_description": self.desc,
|
"long_description": self.desc,
|
||||||
"type": self.type.toName(),
|
# "type": self.type.toName(),
|
||||||
"created_at": self.created_at.isoformat(),
|
"created_at": self.created_at.isoformat(),
|
||||||
|
|
||||||
"license": self.license.name,
|
"license": self.license.name,
|
||||||
|
@ -771,7 +771,7 @@ class MetaPackage(db.Model):
|
||||||
dependencies = db.relationship("Dependency", back_populates="meta_package", lazy="dynamic")
|
dependencies = db.relationship("Dependency", back_populates="meta_package", lazy="dynamic")
|
||||||
packages = db.relationship("Package", lazy="dynamic", back_populates="provides", secondary=PackageProvides)
|
packages = db.relationship("Package", lazy="dynamic", back_populates="provides", secondary=PackageProvides)
|
||||||
|
|
||||||
mp_name_valid = db.CheckConstraint("name ~* '^[a-z0-9_]+$'")
|
mp_name_valid = db.CheckConstraint("name ~* '^[a-zA-Z0-9_\-\.]+$'")
|
||||||
|
|
||||||
def __init__(self, name=None):
|
def __init__(self, name=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -872,48 +872,6 @@ class Tag(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# class MinetestRelease(db.Model):
|
|
||||||
# id = db.Column(db.Integer, primary_key=True)
|
|
||||||
# name = db.Column(db.String(100), unique=True, nullable=False)
|
|
||||||
# protocol = db.Column(db.Integer, nullable=False, default=0)
|
|
||||||
|
|
||||||
# def __init__(self, name=None, protocol=0):
|
|
||||||
# self.name = name
|
|
||||||
# self.protocol = protocol
|
|
||||||
|
|
||||||
# def getActual(self):
|
|
||||||
# return None if self.name == "None" else self
|
|
||||||
|
|
||||||
# def getAsDictionary(self):
|
|
||||||
# return {
|
|
||||||
# "name": self.name,
|
|
||||||
# "protocol_version": self.protocol,
|
|
||||||
# "is_dev": "-dev" in self.name,
|
|
||||||
# }
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def get(cls, version, protocol_num):
|
|
||||||
# if version:
|
|
||||||
# parts = version.strip().split(".")
|
|
||||||
# if len(parts) >= 2:
|
|
||||||
# major_minor = parts[0] + "." + parts[1]
|
|
||||||
# query = MinetestRelease.query.filter(MinetestRelease.name.like("{}%".format(major_minor)))
|
|
||||||
# if protocol_num:
|
|
||||||
# query = query.filter_by(protocol=protocol_num)
|
|
||||||
|
|
||||||
# release = query.one_or_none()
|
|
||||||
# if release:
|
|
||||||
# return release
|
|
||||||
|
|
||||||
# if protocol_num:
|
|
||||||
# # Find the closest matching release
|
|
||||||
# return MinetestRelease.query.order_by(db.desc(MinetestRelease.protocol),
|
|
||||||
# db.desc(MinetestRelease.id)) \
|
|
||||||
# .filter(MinetestRelease.protocol <= protocol_num).first()
|
|
||||||
|
|
||||||
# return None
|
|
||||||
|
|
||||||
|
|
||||||
class PackageRelease(db.Model):
|
class PackageRelease(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
@ -929,11 +887,6 @@ class PackageRelease(db.Model):
|
||||||
downloads = db.Column(db.Integer, nullable=False, default=0)
|
downloads = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
|
||||||
channel = db.Column(db.String(200), nullable=False, default="")
|
channel = db.Column(db.String(200), nullable=False, default="")
|
||||||
# min_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None)
|
|
||||||
# min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id])
|
|
||||||
|
|
||||||
# max_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None)
|
|
||||||
# max_rel = db.relationship("MinetestRelease", foreign_keys=[max_rel_id])
|
|
||||||
|
|
||||||
# If the release is approved, then the task_id must be null and the url must be present
|
# If the release is approved, then the task_id must be null and the url must be present
|
||||||
CK_approval_valid = db.CheckConstraint("not approved OR (task_id IS NULL AND (url = '') IS NOT FALSE)")
|
CK_approval_valid = db.CheckConstraint("not approved OR (task_id IS NULL AND (url = '') IS NOT FALSE)")
|
||||||
|
@ -951,8 +904,6 @@ class PackageRelease(db.Model):
|
||||||
"commit": self.commit_hash,
|
"commit": self.commit_hash,
|
||||||
"downloads": self.downloads,
|
"downloads": self.downloads,
|
||||||
"channel": self.channel,
|
"channel": self.channel,
|
||||||
# "min_minetest_version": self.min_rel and self.min_rel.getAsDictionary(),
|
|
||||||
# "max_minetest_version": self.max_rel and self.max_rel.getAsDictionary(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def getLongAsDictionary(self):
|
def getLongAsDictionary(self):
|
||||||
|
@ -963,8 +914,6 @@ class PackageRelease(db.Model):
|
||||||
"release_date": self.releaseDate.isoformat(),
|
"release_date": self.releaseDate.isoformat(),
|
||||||
"commit": self.commit_hash,
|
"commit": self.commit_hash,
|
||||||
"downloads": self.downloads,
|
"downloads": self.downloads,
|
||||||
# "min_minetest_version": self.min_rel and self.min_rel.getAsDictionary(),
|
|
||||||
# "max_minetest_version": self.max_rel and self.max_rel.getAsDictionary(),
|
|
||||||
"channel": self.channel,
|
"channel": self.channel,
|
||||||
"package": self.package.getAsDictionaryKey()
|
"package": self.package.getAsDictionaryKey()
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -3,7 +3,7 @@ from sqlalchemy import or_
|
||||||
from sqlalchemy.orm import subqueryload
|
from sqlalchemy.orm import subqueryload
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from .models import db, PackageType, Package, ForumTopic, License, PackageRelease, User, Tag, \
|
from .models import db, Package, ForumTopic, License, PackageRelease, User, Tag, \
|
||||||
ContentWarning, PackageState, PackageDevState
|
ContentWarning, PackageState, PackageDevState
|
||||||
from .utils import isYes, get_int_or_abort
|
from .utils import isYes, get_int_or_abort
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ class QueryBuilder:
|
||||||
title = "Packages"
|
title = "Packages"
|
||||||
|
|
||||||
# Get request types
|
# Get request types
|
||||||
types = args.getlist("type")
|
# types = args.getlist("type")
|
||||||
types = [PackageType.get(tname) for tname in types]
|
# types = [PackageType.get(tname) for tname in types]
|
||||||
types = [type for type in types if type is not None]
|
# types = [type for type in types if type is not None]
|
||||||
if len(types) > 0:
|
# if len(types) > 0:
|
||||||
title = ", ".join([str(type.plural) for type in types])
|
# title = ", ".join([str(type.plural) for type in types])
|
||||||
|
|
||||||
# Get tags types
|
# Get tags types
|
||||||
tags = args.getlist("tag")
|
tags = args.getlist("tag")
|
||||||
|
@ -32,7 +32,7 @@ class QueryBuilder:
|
||||||
self.hide_flags = set(args.getlist("hide"))
|
self.hide_flags = set(args.getlist("hide"))
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.types = types
|
# self.types = types
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
|
||||||
self.random = "random" in args
|
self.random = "random" in args
|
||||||
|
@ -126,8 +126,8 @@ class QueryBuilder:
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def filterPackageQuery(self, query):
|
def filterPackageQuery(self, query):
|
||||||
if len(self.types) > 0:
|
# if len(self.types) > 0:
|
||||||
query = query.filter(Package.type.in_(self.types))
|
# query = query.filter(Package.type.in_(self.types))
|
||||||
|
|
||||||
if self.author:
|
if self.author:
|
||||||
author = User.query.filter_by(username=self.author).first()
|
author = User.query.filter_by(username=self.author).first()
|
||||||
|
@ -233,8 +233,8 @@ class QueryBuilder:
|
||||||
query = query.filter(or_(ForumTopic.title.ilike('%' + self.search + '%'),
|
query = query.filter(or_(ForumTopic.title.ilike('%' + self.search + '%'),
|
||||||
ForumTopic.name == self.search.lower()))
|
ForumTopic.name == self.search.lower()))
|
||||||
|
|
||||||
if len(self.types) > 0:
|
# if len(self.types) > 0:
|
||||||
query = query.filter(ForumTopic.type.in_(self.types))
|
# query = query.filter(ForumTopic.type.in_(self.types))
|
||||||
|
|
||||||
if self.limit:
|
if self.limit:
|
||||||
query = query.limit(self.limit)
|
query = query.limit(self.limit)
|
||||||
|
|
|
@ -21,6 +21,16 @@ from app.utils import make_flask_login_password
|
||||||
from app.utils.image import get_image_size
|
from app.utils.image import get_image_size
|
||||||
from app.utils import randomString
|
from app.utils import randomString
|
||||||
|
|
||||||
|
map_categories = {
|
||||||
|
"tools": [ "development", "gtk", "qt" ],
|
||||||
|
"mods": [ "mod", "mods", "extension", "addon" ],
|
||||||
|
"games": [ "games", "game" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
exclude_hashtags = [
|
||||||
|
"game"
|
||||||
|
]
|
||||||
|
|
||||||
#Workaround to get the urls because app.get_urls() doesn't work :|
|
#Workaround to get the urls because app.get_urls() doesn't work :|
|
||||||
def get_urls(app):
|
def get_urls(app):
|
||||||
kinds = [AppStreamGlib.UrlKind(kind) for kind in range(11)]
|
kinds = [AppStreamGlib.UrlKind(kind) for kind in range(11)]
|
||||||
|
@ -80,23 +90,39 @@ def importFromFlathub():
|
||||||
game1.state = PackageState.APPROVED
|
game1.state = PackageState.APPROVED
|
||||||
game1.name = app.get_id()
|
game1.name = app.get_id()
|
||||||
game1.title = app.get_name()
|
game1.title = app.get_name()
|
||||||
if "Development" in app.get_categories():
|
hashtags = []
|
||||||
game1.type = PackageType.TOOL
|
|
||||||
else:
|
|
||||||
game1.type = PackageType.GAME
|
|
||||||
license = "Uknown" if app.get_project_license() is None else app.get_project_license().split("AND")[0].split("and")[0]
|
license = "Uknown" if app.get_project_license() is None else app.get_project_license().split("AND")[0].split("and")[0]
|
||||||
if license not in licenses:
|
if license not in licenses:
|
||||||
row = License(license)
|
row = License(license)
|
||||||
licenses[row.name] = row
|
licenses[row.name] = row
|
||||||
session.add(row)
|
session.add(row)
|
||||||
session.commit()
|
session.commit()
|
||||||
for category in app.get_categories():
|
has_toplevel = False
|
||||||
if category.lower() not in tags:
|
categories = list(set([ x.lower() for x in app.get_categories() ]))
|
||||||
row = Tag(category.lower())
|
added = []
|
||||||
tags[row.name] = row
|
# how do we get the type attribute from <component> here?
|
||||||
print("adding tag: ", row.name)
|
# if app.get_type() == "addon":
|
||||||
session.add(row)
|
# game1.tags.append(tags["mods"])
|
||||||
game1.tags.append(tags[category.lower()])
|
# added.append("mods")
|
||||||
|
# has_toplevel = True
|
||||||
|
for category in categories:
|
||||||
|
if category in tags and category not in added:
|
||||||
|
if category in map_categories:
|
||||||
|
has_toplevel = True
|
||||||
|
game1.tags.append(tags[category])
|
||||||
|
added.append(category)
|
||||||
|
elif category not in exclude_hashtags:
|
||||||
|
hashtags.append(category)
|
||||||
|
if not has_toplevel:
|
||||||
|
for map_category in map_categories:
|
||||||
|
if category in map_categories[map_category] and map_category not in added:
|
||||||
|
game1.tags.append(tags[map_category])
|
||||||
|
added.append(map_category)
|
||||||
|
has_toplevel = True
|
||||||
|
break
|
||||||
|
if not has_toplevel and "games" not in added:
|
||||||
|
game1.tags.append(tags["games"])
|
||||||
|
added.append("games")
|
||||||
|
|
||||||
# this short list seems like a reasonable set of initial "featured" games
|
# this short list seems like a reasonable set of initial "featured" games
|
||||||
if app.get_id() in alwaysAccept:
|
if app.get_id() in alwaysAccept:
|
||||||
|
@ -110,12 +136,16 @@ def importFromFlathub():
|
||||||
for url,t in urls:
|
for url,t in urls:
|
||||||
if t == "bugtracker":
|
if t == "bugtracker":
|
||||||
game1.issueTracker = url
|
game1.issueTracker = url
|
||||||
elif t == "homepage":
|
if "git" in url and "issues" in url:
|
||||||
|
game1.repo = url.replace("/issues", "")
|
||||||
|
elif t == "homepage" and not game1.repo:
|
||||||
game1.repo = url
|
game1.repo = url
|
||||||
|
if t == "homepage" and "git" not in url:
|
||||||
|
game1.website = url
|
||||||
|
|
||||||
game1.forums = 12835
|
game1.forums = 12835
|
||||||
game1.short_desc = "" or app.get_comment()
|
game1.short_desc = "" or app.get_comment()
|
||||||
game1.desc = app.get_description()
|
game1.desc = app.get_description() + "\n " + ",".join([ "#" + x for x in hashtags ])
|
||||||
game1.install_desc = "Make sure to follow the [setup guide](https://flatpak.org/setup/) before installing. \n"
|
game1.install_desc = "Make sure to follow the [setup guide](https://flatpak.org/setup/) before installing. \n"
|
||||||
game1.install_desc += f"\n```\nflatpak install flathub {app.get_id()}\n```\n"
|
game1.install_desc += f"\n```\nflatpak install flathub {app.get_id()}\n```\n"
|
||||||
game1.install_desc += "Run: \n"
|
game1.install_desc += "Run: \n"
|
||||||
|
|
|
@ -117,76 +117,76 @@ def getLinksFromModSearch():
|
||||||
|
|
||||||
return links
|
return links
|
||||||
|
|
||||||
@celery.task()
|
# @celery.task()
|
||||||
def importTopicList():
|
# def importTopicList():
|
||||||
links_by_id = getLinksFromModSearch()
|
# links_by_id = getLinksFromModSearch()
|
||||||
|
|
||||||
info_by_id = {}
|
# info_by_id = {}
|
||||||
getTopicsFromForum(11, out=info_by_id, extra={ 'type': PackageType.TOOL, 'wip': False })
|
# getTopicsFromForum(11, out=info_by_id, extra={ 'type': PackageType.TOOL, 'wip': False })
|
||||||
getTopicsFromForum(9, out=info_by_id, extra={ 'type': PackageType.TOOL, 'wip': True })
|
# getTopicsFromForum(9, out=info_by_id, extra={ 'type': PackageType.TOOL, 'wip': True })
|
||||||
getTopicsFromForum(15, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': False })
|
# getTopicsFromForum(15, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': False })
|
||||||
getTopicsFromForum(50, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': True })
|
# getTopicsFromForum(50, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': True })
|
||||||
|
|
||||||
# Caches
|
# # Caches
|
||||||
username_to_user = {}
|
# username_to_user = {}
|
||||||
topics_by_id = {}
|
# topics_by_id = {}
|
||||||
for topic in ForumTopic.query.all():
|
# for topic in ForumTopic.query.all():
|
||||||
topics_by_id[topic.topic_id] = topic
|
# topics_by_id[topic.topic_id] = topic
|
||||||
|
|
||||||
def get_or_create_user(username):
|
# def get_or_create_user(username):
|
||||||
user = username_to_user.get(username)
|
# user = username_to_user.get(username)
|
||||||
if user:
|
# if user:
|
||||||
return user
|
# return user
|
||||||
|
|
||||||
if not is_username_valid(username):
|
# if not is_username_valid(username):
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
user = User.query.filter_by(forums_username=username).first()
|
# user = User.query.filter_by(forums_username=username).first()
|
||||||
if user is None:
|
# if user is None:
|
||||||
user = User.query.filter_by(username=username).first()
|
# user = User.query.filter_by(username=username).first()
|
||||||
if user:
|
# if user:
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
user = User(username)
|
# user = User(username)
|
||||||
user.forums_username = username
|
# user.forums_username = username
|
||||||
db.session.add(user)
|
# db.session.add(user)
|
||||||
|
|
||||||
username_to_user[username] = user
|
# username_to_user[username] = user
|
||||||
return user
|
# return user
|
||||||
|
|
||||||
# Create or update
|
# # Create or update
|
||||||
for info in info_by_id.values():
|
# for info in info_by_id.values():
|
||||||
id = int(info["id"])
|
# id = int(info["id"])
|
||||||
|
|
||||||
# Get author
|
# # Get author
|
||||||
username = info["author"]
|
# username = info["author"]
|
||||||
user = get_or_create_user(username)
|
# user = get_or_create_user(username)
|
||||||
if user is None:
|
# if user is None:
|
||||||
print("Error! Unable to create user {}".format(username), file=sys.stderr)
|
# print("Error! Unable to create user {}".format(username), file=sys.stderr)
|
||||||
continue
|
# continue
|
||||||
|
|
||||||
# Get / add row
|
# # Get / add row
|
||||||
topic = topics_by_id.get(id)
|
# topic = topics_by_id.get(id)
|
||||||
if topic is None:
|
# if topic is None:
|
||||||
topic = ForumTopic()
|
# topic = ForumTopic()
|
||||||
db.session.add(topic)
|
# db.session.add(topic)
|
||||||
|
|
||||||
# Parse title
|
# # Parse title
|
||||||
title, name = parseTitle(info["title"])
|
# title, name = parseTitle(info["title"])
|
||||||
|
|
||||||
# Get link
|
# # Get link
|
||||||
link = links_by_id.get(id)
|
# link = links_by_id.get(id)
|
||||||
|
|
||||||
# Fill row
|
# # Fill row
|
||||||
topic.topic_id = int(id)
|
# topic.topic_id = int(id)
|
||||||
topic.author = user
|
# topic.author = user
|
||||||
topic.type = info["type"]
|
# topic.type = info["type"]
|
||||||
topic.title = title
|
# topic.title = title
|
||||||
topic.name = name
|
# topic.name = name
|
||||||
topic.link = link
|
# topic.link = link
|
||||||
topic.wip = info["wip"]
|
# topic.wip = info["wip"]
|
||||||
topic.posts = int(info["posts"])
|
# topic.posts = int(info["posts"])
|
||||||
topic.views = int(info["views"])
|
# topic.views = int(info["views"])
|
||||||
topic.created_at = info["date"]
|
# topic.created_at = info["date"]
|
||||||
|
|
||||||
db.session.commit()
|
# db.session.commit()
|
||||||
|
|
|
@ -75,8 +75,7 @@ def getMeta(urlstr, author):
|
||||||
|
|
||||||
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, author=release.package.author.username, name=release.package.name)
|
||||||
author=release.package.author.username, name=release.package.name)
|
|
||||||
|
|
||||||
|
|
||||||
cache = {}
|
cache = {}
|
||||||
|
@ -109,9 +108,9 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
|
||||||
db.session.add(Dependency(package, meta=meta, optional=True))
|
db.session.add(Dependency(package, meta=meta, optional=True))
|
||||||
|
|
||||||
# Update game supports
|
# Update game supports
|
||||||
if package.type == PackageType.TOOL:
|
# if package.type == PackageType.TOOL:
|
||||||
resolver = GameSupportResolver()
|
# resolver = GameSupportResolver()
|
||||||
resolver.update(package)
|
# resolver.update(package)
|
||||||
|
|
||||||
# # Update min/max
|
# # Update min/max
|
||||||
# if tree.meta.get("min_minetest_version"):
|
# if tree.meta.get("min_minetest_version"):
|
||||||
|
|
|
@ -6,32 +6,29 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/libs/bootstrap.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/libs/bootstrap.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=34">
|
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=35">
|
||||||
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
|
||||||
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
<link rel="icon" href="/static/lg-logo.png" sizes="290x290">
|
||||||
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
|
||||||
<link rel="icon" href="/favicon-32.png" sizes="32x32">
|
|
||||||
{% block headextra %}{% endblock %}
|
{% block headextra %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand" href="/">{{ config.USER_APP_NAME }}</a>
|
<a class="navbar-brand" href="/">
|
||||||
|
<img src="/static/lg-logo.png" width="30" height="30" class="d-inline-block align-top" alt="">
|
||||||
|
{{ config.USER_APP_NAME }}
|
||||||
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarColor01">
|
<div class="collapse navbar-collapse" id="navbarColor01">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
|
{% for tag in toplevel %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('packages.list_all', tag='game') }}">{{ _("Games") }}</a>
|
<a class="nav-link" href="{{ url_for('packages.list_all', tag=tag.name) }}">{{ _(tag.title) }}</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{{ url_for('packages.list_all', tag='development') }}">{{ _("Tools") }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{{ url_for('packages.list_all', tag='gtk') }}">{{ _("Asset Packs") }}</a>
|
|
||||||
</li>
|
</li>
|
||||||
|
{% endfor %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('packages.list_all', random=1, lucky=1) }}">{{ _("Random") }}</a>
|
<a class="nav-link" href="{{ url_for('packages.list_all', random=1, lucky=1) }}">{{ _("Random") }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -45,7 +42,7 @@
|
||||||
<form class="form-inline my-2 my-lg-0" method="GET" action="/packages/">
|
<form class="form-inline my-2 my-lg-0" method="GET" action="/packages/">
|
||||||
{% if type %}<input type="hidden" name="type" value="{{ type }}" />{% endif %}
|
{% if type %}<input type="hidden" name="type" value="{{ type }}" />{% endif %}
|
||||||
<input class="form-control" name="q" type="text"
|
<input class="form-control" name="q" type="text"
|
||||||
placeholder="{% if query_hint %}{{ _('Search %(type)s', type=query_hint | lower) }}{% else %}{{ _('Search all packages') }}{% endif %}"
|
placeholder="{% if query_hint %}{{ _('Search projects', type=query_hint | lower) }}{% else %}{{ _('Search all packages') }}{% endif %}"
|
||||||
value="{{ query or ''}}">
|
value="{{ query or ''}}">
|
||||||
<input class="btn btn-secondary my-2 my-sm-0 mr-sm-2" type="submit" value="{{ _('Search') }}" />
|
<input class="btn btn-secondary my-2 my-sm-0 mr-sm-2" type="submit" value="{{ _('Search') }}" />
|
||||||
<!-- <input class="btn btn-secondary my-2 my-sm-0"
|
<!-- <input class="btn btn-secondary my-2 my-sm-0"
|
||||||
|
|
|
@ -34,5 +34,5 @@
|
||||||
{{ _("Unsubscribe") }}
|
{{ _("Unsubscribe") }}
|
||||||
</a> <br>
|
</a> <br>
|
||||||
|
|
||||||
{{ _("This is a '%(type)s' notification.", type=notification.type.getTitle()) }}
|
{{ _("This is a 'projects' notification.", type=notification.type.getTitle()) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -38,17 +38,11 @@
|
||||||
alt="{{ _('%(title)s by %(author)s', title=package.title, author=package.author.display_name) }}">
|
alt="{{ _('%(title)s by %(author)s', title=package.title, author=package.author.display_name) }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="carousel-caption text-shadow">
|
<div class="carousel-caption text-shadow">
|
||||||
<h3 class="mt-0 mb-3">
|
|
||||||
<strong>{{ package.title }}</strong>
|
|
||||||
</h3>
|
|
||||||
<p>
|
<p>
|
||||||
{{ package.short_desc }}
|
{{ package.short_desc }}
|
||||||
</p>
|
</p>
|
||||||
{% if package.author %}
|
{% if package.author %}
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-block">
|
||||||
<span class="mr-2">
|
|
||||||
{{ package.type.text }}
|
|
||||||
</span>
|
|
||||||
{% for warning in package.content_warnings %}
|
{% for warning in package.content_warnings %}
|
||||||
<span class="badge badge-warning" title="{{ warning.description }}">
|
<span class="badge badge-warning" title="{{ warning.description }}">
|
||||||
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||||
|
@ -106,27 +100,13 @@
|
||||||
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
||||||
{{ render_pkggrid(updated) }}
|
{{ render_pkggrid(updated) }}
|
||||||
|
|
||||||
|
{% for tag in toplevel %}
|
||||||
<a href="{{ url_for('packages.list_all', type='game', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
<a href="{{ url_for('packages.list_all', tag=tag.name, sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||||
{{ _("See more") }}
|
{{ _("See more") }}
|
||||||
</a>
|
</a>
|
||||||
<h2 class="my-3">{{ _("Top Games") }}</h2>
|
<h2 class="my-3">{{ _("Top " + tag.title) }}</h2>
|
||||||
{{ render_pkggrid(pop_gam) }}
|
{{ render_pkggrid(popular[tag.name]) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<a href="{{ url_for('packages.list_all', type='tool', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
|
||||||
{{ _("See more") }}
|
|
||||||
</a>
|
|
||||||
<h2 class="my-3">{{ _("Top Tools") }}</h2>
|
|
||||||
{{ render_pkggrid(pop_mod) }}
|
|
||||||
|
|
||||||
|
|
||||||
<a href="{{ url_for('packages.list_all', type='asset_pack', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
|
||||||
{{ _("See more") }}
|
|
||||||
</a>
|
|
||||||
<h2 class="my-3">{{ _("Top Asset Packs") }}</h2>
|
|
||||||
{{ render_pkggrid(pop_txp) }}
|
|
||||||
|
|
||||||
|
|
||||||
<h2 class="my-3">{{ _("Search by Tags") }}</h2>
|
<h2 class="my-3">{{ _("Search by Tags") }}</h2>
|
||||||
{% for pair in tags %}
|
{% for pair in tags %}
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endset %}
|
{% endset %}
|
||||||
|
|
||||||
{% elif (package.type == package.type.GAME or package.type == package.type.ASSETPACK) and package.screenshots.count() == 0 %}
|
{% elif package.screenshots.count() == 0 %}
|
||||||
{% set message = _("You need to add at least one screenshot.") %}
|
{% set message = _("You need to add at least one screenshot.") %}
|
||||||
|
|
||||||
{% elif package.getMissingHardDependenciesQuery().count() > 0 %}
|
{% elif package.getMissingHardDependenciesQuery().count() > 0 %}
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
{{ package.short_desc }}
|
{{ package.short_desc }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if not package.license.is_foss and not package.media_license.is_foss and package.type != package.type.ASSETPACK %}
|
{% if not package.license.is_foss and not package.media_license.is_foss %}
|
||||||
<p style="color:#f33;">
|
<p style="color:#f33;">
|
||||||
{{ _("<b>Warning:</b> Non-free code and media.") }}
|
{{ _("<b>Warning:</b> Non-free code and media.") }}
|
||||||
</p>
|
</p>
|
||||||
{% elif not package.license.is_foss and package.type != package.type.ASSETPACK %}
|
{% elif not package.license.is_foss %}
|
||||||
<p style="color:#f33;">
|
<p style="color:#f33;">
|
||||||
{{ _("<b>Warning:</b> Non-free code.") }}
|
{{ _("<b>Warning:</b> Non-free code.") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
<form method="post" action="{{ package.getURL("packages.review") }}" class="card-body">
|
<form method="post" action="{{ package.getURL("packages.review") }}" class="card-body">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<p>
|
<p>
|
||||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
{{ _("Do you recommend this?") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
<form method="post" action="{{ package.getURL("packages.review") }}" class="card-body">
|
<form method="post" action="{{ package.getURL("packages.review") }}" class="card-body">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
<p>
|
<p>
|
||||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
{{ _("Do you recommend this?") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
<div class="alert alert-secondary">
|
<div class="alert alert-secondary">
|
||||||
<a class="float-right btn btn-sm btn-default" href="/help/package_config/#cdbjson">{{ _("Read more") }}</a>
|
<a class="float-right btn btn-sm btn-default" href="/help/package_config/#cdbjson">{{ _("Read more") }}</a>
|
||||||
|
|
||||||
{{ _("You can include a .cdb.json file in your %(type)s to update these details automatically.", type=package.type.text.lower()) }}
|
{{ _("You can include a .cdb.json file in your projects to update these details automatically.") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,13 @@
|
||||||
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
||||||
{{ render_pkggrid(updated) }}
|
{{ render_pkggrid(updated) }}
|
||||||
|
|
||||||
|
{% for tag in toplevel %}
|
||||||
<a href="{{ url_for('packages.list_all', type='tool', sort='score', order='desc', game=package.getId()) }}" class="btn btn-secondary float-right">
|
<a href="{{ url_for('packages.list_all', tag=tag.name, sort='score', order='desc', game=package.getId()) }}" class="btn btn-secondary float-right">
|
||||||
{{ _("See more") }}
|
{{ _("See more") }}
|
||||||
</a>
|
</a>
|
||||||
<h2 class="my-3">{{ _("Top Tools") }}</h2>
|
<h2 class="my-3">{{ _("Top " + tag.title) }}</h2>
|
||||||
{{ render_pkggrid(pop_mod) }}
|
{{ render_pkggrid(popular[tag.name]) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<a href="{{ url_for('packages.list_all', sort='reviews', order='desc', game=package.getId()) }}" class="btn btn-secondary float-right">
|
<a href="{{ url_for('packages.list_all', sort='reviews', order='desc', game=package.getId()) }}" class="btn btn-secondary float-right">
|
||||||
{{ _("See more") }}
|
{{ _("See more") }}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{% block author_links %}
|
{% block author_links %}
|
||||||
{% if authors %}
|
{% if authors %}
|
||||||
{% for author in authors %}
|
{% for author in authors %}
|
||||||
<a href="{{ url_for('packages.list_all', type=type, author=author[0], q=author[1]) }}">{{ author[0] }}</a>
|
<a href="{{ url_for('packages.list_all', author=author[0], q=author[1]) }}">{{ author[0] }}</a>
|
||||||
{% if not loop.last %}
|
{% if not loop.last %}
|
||||||
,
|
,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
<h1>{{ self.title() }}</h1>
|
<h1>{{ self.title() }}</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ _("A release is a single downloadable version of your %(title)s.", title=package.type.text.lower()) }}
|
{{ _("A release is a single downloadable version of your project") }}
|
||||||
{{ _("You need to create releases even if you use a rolling release development cycle, as Minetest needs them to check for updates.") }}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if package.repo %}
|
{% if package.repo %}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
{{ _("Do you recommend this?") }}
|
||||||
</p>
|
</p>
|
||||||
{{ render_toggle_field(form.recommends, icons={"yes":"fa-thumbs-up", "no":"fa-thumbs-down"}) }}
|
{{ render_toggle_field(form.recommends, icons={"yes":"fa-thumbs-up", "no":"fa-thumbs-down"}) }}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,9 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block container %}
|
{% block container %}
|
||||||
{% if not package.license.is_foss and not package.media_license.is_foss and package.type != package.type.ASSETPACK %}
|
{% if not package.license.is_foss and not package.media_license.is_foss %}
|
||||||
{% set package_warning=_("Non-free code and media") %}
|
{% set package_warning=_("Non-free code and media") %}
|
||||||
{% elif not package.license.is_foss and package.type != package.type.ASSETPACK %}
|
{% elif not package.license.is_foss %}
|
||||||
{% set package_warning=_("Non-free code") %}
|
{% set package_warning=_("Non-free code") %}
|
||||||
{% elif not package.media_license.is_foss %}
|
{% elif not package.media_license.is_foss %}
|
||||||
{% set package_warning=_("Non-free media") %}
|
{% set package_warning=_("Non-free media") %}
|
||||||
|
@ -230,13 +230,13 @@
|
||||||
{% for ss in screenshots %}
|
{% for ss in screenshots %}
|
||||||
{% if ss.approved or package.checkPerm(current_user, "ADD_SCREENSHOTS") %}
|
{% if ss.approved or package.checkPerm(current_user, "ADD_SCREENSHOTS") %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ ss.url }}" class="gallery-image">
|
<a href="{{ss.url}}" class="gallery-image" data-toggle="modal" data-target="#screenshot_{{ss.id}}">
|
||||||
<img src="{{ ss.getThumbnailURL() }}" alt="{{ ss.title }}" />
|
<img src="{{ ss.getThumbnailURL() }}" alt="{{ ss.title }}" />
|
||||||
{% if not ss.approved %}
|
{% if not ss.approved %}
|
||||||
<span class="badge bg-dark badge-tr">{{ _("Awaiting review") }}</span>
|
<span class="badge bg-dark badge-tr">{{ _("Awaiting review") }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
|
@ -307,12 +307,10 @@
|
||||||
{{ render_pkggrid(packages_uses) }}
|
{{ render_pkggrid(packages_uses) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if package.type == package.type.GAME %}
|
<h2>{{ _("Content") }}</h2>
|
||||||
<h2>{{ _("Content") }}</h2>
|
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-primary">
|
||||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-primary">
|
{{ _("View content for game") }}
|
||||||
{{ _("View content for game") }}
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="col-md-3 info-sidebar">
|
<aside class="col-md-3 info-sidebar">
|
||||||
|
@ -362,92 +360,81 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if package.type == package.type.GAME %}
|
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-block mb-4 btn-primary">
|
||||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-block mb-4 btn-primary">
|
{{ _("View content for game") }}
|
||||||
{{ _("View content for game") }}
|
</a>
|
||||||
</a>
|
<h3>{{ _("Dependencies") }}</h3>
|
||||||
{% endif %}
|
<dl>
|
||||||
|
<dt>{{ _("Required") }}</dt>
|
||||||
|
<dd>
|
||||||
|
{% for dep in package.getSortedHardDependencies() %}
|
||||||
|
{%- if dep.package %}
|
||||||
|
<a class="badge badge-primary"
|
||||||
|
href="{{ dep.package.getURL("packages.view") }}">
|
||||||
|
{{ _("%(title)s by %(display_name)s",
|
||||||
|
title=dep.package.title, display_name=dep.package.author.display_name) }}
|
||||||
|
</a>
|
||||||
|
{% elif dep.meta_package %}
|
||||||
|
<a class="badge badge-primary"
|
||||||
|
href="{{ url_for('metapackages.view', name=dep.meta_package.name) }}">
|
||||||
|
{{ dep.meta_package.name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ "Expected package or meta_package in dep!" | throw }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{{ _("No required dependencies") }}
|
||||||
|
{% endfor %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
{% if package.type != package.type.ASSETPACK %}
|
{% set optional_deps=package.getSortedOptionalDependencies() %}
|
||||||
<h3>{{ _("Dependencies") }}</h3>
|
{% if optional_deps %}
|
||||||
<dl>
|
<dt>{{ _("Optional") }}</dt>
|
||||||
<dt>{{ _("Required") }}</dt>
|
|
||||||
<dd>
|
<dd>
|
||||||
{% for dep in package.getSortedHardDependencies() %}
|
{% for dep in optional_deps %}
|
||||||
{%- if dep.package %}
|
{%- if dep.package %}
|
||||||
<a class="badge badge-primary"
|
<a class="badge badge-secondary"
|
||||||
href="{{ dep.package.getURL("packages.view") }}">
|
href="{{ dep.package.getURL("packages.view") }}">
|
||||||
{{ _("%(title)s by %(display_name)s",
|
{{ _("%(title)s by %(display_name)s",
|
||||||
title=dep.package.title, display_name=dep.package.author.display_name) }}
|
title=dep.package.title, display_name=dep.package.author.display_name) }}
|
||||||
</a>
|
|
||||||
{% elif dep.meta_package %}
|
{% elif dep.meta_package %}
|
||||||
<a class="badge badge-primary"
|
<a class="badge badge-secondary"
|
||||||
href="{{ url_for('metapackages.view', name=dep.meta_package.name) }}">
|
href="{{ url_for('metapackages.view', name=dep.meta_package.name) }}">
|
||||||
{{ dep.meta_package.name }}
|
{{ dep.meta_package.name }}
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ "Expected package or meta_package in dep!" | throw }}
|
{{ "Expected package or meta_package in dep!" | throw }}
|
||||||
{% endif %}
|
{% endif %}</a>
|
||||||
{% else %}
|
|
||||||
{{ _("No required dependencies") }}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dd>
|
</dd>
|
||||||
|
{% endif %}
|
||||||
|
</dl>
|
||||||
|
|
||||||
{% set optional_deps=package.getSortedOptionalDependencies() %}
|
<h3>{{ _("Compatible Games") }}</h3>
|
||||||
{% if optional_deps %}
|
{% for support in package.getSortedSupportedGames() %}
|
||||||
<dt>{{ _("Optional") }}</dt>
|
<a class="badge badge-secondary"
|
||||||
<dd>
|
href="{{ support.game.getURL('packages.view') }}">
|
||||||
{% for dep in optional_deps %}
|
{{ _("%(title)s by %(display_name)s",
|
||||||
{%- if dep.package %}
|
title=support.game.title, display_name=support.game.author.display_name) }}
|
||||||
<a class="badge badge-secondary"
|
</a>
|
||||||
href="{{ dep.package.getURL("packages.view") }}">
|
{% else %}
|
||||||
{{ _("%(title)s by %(display_name)s",
|
{{ _("No specific game is required") }}
|
||||||
title=dep.package.title, display_name=dep.package.author.display_name) }}
|
{% endfor %}
|
||||||
{% elif dep.meta_package %}
|
<p class="text-muted small mt-2 mb-0">
|
||||||
<a class="badge badge-secondary"
|
{{ _("This is an experimental feature.") }}
|
||||||
href="{{ url_for('metapackages.view', name=dep.meta_package.name) }}">
|
{{ _("Supported games are determined by an algorithm, and may not be correct.") }}
|
||||||
{{ dep.meta_package.name }}
|
</p>
|
||||||
{% else %}
|
|
||||||
{{ "Expected package or meta_package in dep!" | throw }}
|
|
||||||
{% endif %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</dd>
|
|
||||||
{% endif %}
|
|
||||||
</dl>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if package.type == package.type.TOOL %}
|
|
||||||
<h3>{{ _("Compatible Games") }}</h3>
|
|
||||||
{% for support in package.getSortedSupportedGames() %}
|
|
||||||
<a class="badge badge-secondary"
|
|
||||||
href="{{ support.game.getURL('packages.view') }}">
|
|
||||||
{{ _("%(title)s by %(display_name)s",
|
|
||||||
title=support.game.title, display_name=support.game.author.display_name) }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{{ _("No specific game is required") }}
|
|
||||||
{% endfor %}
|
|
||||||
<p class="text-muted small mt-2 mb-0">
|
|
||||||
{{ _("This is an experimental feature.") }}
|
|
||||||
{{ _("Supported games are determined by an algorithm, and may not be correct.") }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
{{ _("Information") }}
|
{{ _("Information") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt>{{ _("Type") }}</dt>
|
|
||||||
<dd>{{ package.type.text }}</dd>
|
|
||||||
<dt>{{ _("Technical Name") }}</dt>
|
<dt>{{ _("Technical Name") }}</dt>
|
||||||
<dd>{{ package.name }}</dd>
|
<dd>{{ package.name }}</dd>
|
||||||
<dt>{{ _("License") }}</dt>
|
<dt>{{ _("License") }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{% if package.license == package.media_license %}
|
{% if package.license == package.media_license %}
|
||||||
{{ render_license(package.license) }}
|
{{ render_license(package.license) }}
|
||||||
{% elif package.type == package.type.ASSETPACK %}
|
|
||||||
{{ render_license(package.media_license) }}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ _("%(code_license)s for code,<br>%(media_license)s for media.",
|
{{ _("%(code_license)s for code,<br>%(media_license)s for media.",
|
||||||
code_license=render_license(package.license), media_license=render_license(package.media_license)) }}
|
code_license=render_license(package.license), media_license=render_license(package.media_license)) }}
|
||||||
|
@ -536,5 +523,25 @@
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{% for ss in screenshots %}
|
||||||
|
<div class="modal fade" id="screenshot_{{ss.id}}" tabindex="-1" aria-labelledby="screenshot" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="screenshot title">"{{ ss.title }}"</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<img src="{{ ss.url }}" alt="{{ ss.title }}" style="width: 100%"/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<i class="fab fa-github mr-1"></i>
|
<i class="fab fa-github mr-1"></i>
|
||||||
{{ _("GitHub") }}
|
{{ _("GitHub") }}
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-secondary" href="{{ url_for('users.claim') }}">
|
<a class="btn btn-secondary" href="{{ url_for('users.register') }}">
|
||||||
<i class="fas fa-user-plus mr-1"></i>
|
<i class="fas fa-user-plus mr-1"></i>
|
||||||
{{ _("Register") }}
|
{{ _("Register") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<img src="/static/puzzle.png" />
|
<img src="{{captcha}}" />
|
||||||
</p>
|
</p>
|
||||||
{{ render_field(form.question, hint=_("Please prove that you are human")) }}
|
{{ render_field(form.question, hint=_("Please prove that you are human")) }}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
from app.default_data import populate_test_data
|
from app.default_data import populate_test_data
|
||||||
from app.models import db, License, PackageType, User, Package, PackageState, PackageRelease, MinetestRelease
|
from app.models import db, License, User, Package, PackageState, PackageRelease, MinetestRelease
|
||||||
from .utils import parse_json, validate_package_list
|
from .utils import parse_json, validate_package_list
|
||||||
from .utils import client # noqa
|
from .utils import client # noqa
|
||||||
|
|
||||||
|
@ -16,30 +16,30 @@ def make_package(name: str, versions: List[Tuple[Optional[str], Optional[str]]])
|
||||||
tool.title = name
|
tool.title = name
|
||||||
tool.license = license
|
tool.license = license
|
||||||
tool.media_license = license
|
tool.media_license = license
|
||||||
tool.type = PackageType.TOOL
|
# tool.type = PackageType.TOOL
|
||||||
tool.author = author
|
tool.author = author
|
||||||
tool.short_desc = "The content library should not be used yet as it is still in alpha"
|
tool.short_desc = "The content library should not be used yet as it is still in alpha"
|
||||||
tool.desc = "This is the long desc"
|
tool.desc = "This is the long desc"
|
||||||
db.session.add(tool)
|
db.session.add(tool)
|
||||||
|
|
||||||
rels = []
|
# rels = []
|
||||||
|
|
||||||
for (minv, maxv) in versions:
|
# for (minv, maxv) in versions:
|
||||||
rel = PackageRelease()
|
# rel = PackageRelease()
|
||||||
rel.package = tool
|
# rel.package = tool
|
||||||
rel.title = "test"
|
# rel.title = "test"
|
||||||
rel.url = "https://github.com/ezhh/handholds/archive/master.zip"
|
# rel.url = "https://github.com/ezhh/handholds/archive/master.zip"
|
||||||
|
|
||||||
# if minv:
|
# # if minv:
|
||||||
# rel.min_rel = MinetestRelease.query.filter_by(name=minv).first()
|
# # rel.min_rel = MinetestRelease.query.filter_by(name=minv).first()
|
||||||
# assert rel.min_rel
|
# # assert rel.min_rel
|
||||||
# if maxv:
|
# # if maxv:
|
||||||
# rel.max_rel = MinetestRelease.query.filter_by(name=maxv).first()
|
# # rel.max_rel = MinetestRelease.query.filter_by(name=maxv).first()
|
||||||
# assert rel.max_rel
|
# # assert rel.max_rel
|
||||||
|
|
||||||
rel.approved = True
|
# rel.approved = True
|
||||||
db.session.add(rel)
|
# db.session.add(rel)
|
||||||
rels.append(rel)
|
# rels.append(rel)
|
||||||
|
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ from .user import *
|
||||||
|
|
||||||
YESES = ["yes", "true", "1", "on"]
|
YESES = ["yes", "true", "1", "on"]
|
||||||
|
|
||||||
|
def get_toplevel_tags():
|
||||||
|
return Tag.query.filter_by(is_toplevel=True).order_by(db.asc(Tag.id)).all()
|
||||||
|
|
||||||
|
|
||||||
def is_username_valid(username):
|
def is_username_valid(username):
|
||||||
return username is not None and len(username) >= 2 and re.match(r"^[A-Za-z0-9._-]*$", username)
|
return username is not None and len(username) >= 2 and re.match(r"^[A-Za-z0-9._-]*$", username)
|
||||||
|
|
|
@ -46,8 +46,8 @@ alwaysAccept = [
|
||||||
'org.freecol.FreeCol',
|
'org.freecol.FreeCol',
|
||||||
'org.freeciv.Freeciv',
|
'org.freeciv.Freeciv',
|
||||||
'io.github.EndlessSky.endless-sky',
|
'io.github.EndlessSky.endless-sky',
|
||||||
'org.frozen_bubble.frozen-bubble',
|
|
||||||
'org.kde.ksudoku',
|
'org.kde.ksudoku',
|
||||||
|
'net.veloren.veloren'
|
||||||
]
|
]
|
||||||
|
|
||||||
alwaysDeny = [
|
alwaysDeny = [
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import abort, redirect, url_for, request
|
from flask import abort, redirect, url_for, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app.models import User, NotificationType, Package, UserRank, Notification, db, AuditSeverity, AuditLogEntry, ThreadReply, Thread, PackageState, PackageType, PackageAlias
|
from app.models import User, NotificationType, Package, UserRank, Notification, db, AuditSeverity, AuditLogEntry, ThreadReply, Thread, PackageState, PackageAlias
|
||||||
|
|
||||||
|
|
||||||
def getPackageByInfo(author, name):
|
def getPackageByInfo(author, name):
|
||||||
|
@ -45,7 +45,7 @@ def is_package_page(f):
|
||||||
package = getPackageByInfo(author, name)
|
package = getPackageByInfo(author, name)
|
||||||
if package is None:
|
if package is None:
|
||||||
package = getPackageByInfo(author, name + "_game")
|
package = getPackageByInfo(author, name + "_game")
|
||||||
if package and package.type == PackageType.GAME:
|
if package:
|
||||||
args = dict(kwargs)
|
args = dict(kwargs)
|
||||||
args["name"] = name + "_game"
|
args["name"] = name + "_game"
|
||||||
return redirect(url_for(request.endpoint, **args))
|
return redirect(url_for(request.endpoint, **args))
|
||||||
|
|
|
@ -18,6 +18,37 @@ depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
|
command = """
|
||||||
|
CREATE OR REPLACE FUNCTION parse_websearch(config regconfig, search_query text)
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
SELECT
|
||||||
|
string_agg(
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN position('''' IN words.word) > 0 THEN CONCAT(words.word, ':*')
|
||||||
|
ELSE words.word
|
||||||
|
END
|
||||||
|
),
|
||||||
|
' '
|
||||||
|
)::tsquery
|
||||||
|
FROM (
|
||||||
|
SELECT trim(
|
||||||
|
regexp_split_to_table(
|
||||||
|
websearch_to_tsquery(config, lower(search_query))::text,
|
||||||
|
' '
|
||||||
|
)
|
||||||
|
) AS word
|
||||||
|
) AS words
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION parse_websearch(search_query text)
|
||||||
|
RETURNS tsquery AS $$
|
||||||
|
SELECT parse_websearch('pg_catalog.simple', search_query);
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;"""
|
||||||
|
|
||||||
|
op.execute(command)
|
||||||
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('content_warning',
|
op.create_table('content_warning',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
@ -96,7 +127,6 @@ def upgrade():
|
||||||
sa.Column('desc', sa.UnicodeText(), nullable=True),
|
sa.Column('desc', sa.UnicodeText(), nullable=True),
|
||||||
sa.Column('build_desc', sa.UnicodeText(), nullable=True),
|
sa.Column('build_desc', sa.UnicodeText(), nullable=True),
|
||||||
sa.Column('install_desc', sa.UnicodeText(), nullable=True),
|
sa.Column('install_desc', sa.UnicodeText(), nullable=True),
|
||||||
sa.Column('type', sa.Enum('GAME', 'TOOL', 'ASSETPACK', name='packagetype'), nullable=False),
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||||
sa.Column('approved_at', sa.DateTime(), nullable=True),
|
sa.Column('approved_at', sa.DateTime(), nullable=True),
|
||||||
sa.Column('search_vector', sqlalchemy_utils.types.ts_vector.TSVectorType(), nullable=True),
|
sa.Column('search_vector', sqlalchemy_utils.types.ts_vector.TSVectorType(), nullable=True),
|
||||||
|
@ -113,9 +143,7 @@ def upgrade():
|
||||||
sa.Column('issueTracker', sa.String(length=200), nullable=True),
|
sa.Column('issueTracker', sa.String(length=200), nullable=True),
|
||||||
sa.Column('forums', sa.Integer(), nullable=True),
|
sa.Column('forums', sa.Integer(), nullable=True),
|
||||||
sa.Column('video_url', sa.String(length=200), nullable=True),
|
sa.Column('video_url', sa.String(length=200), nullable=True),
|
||||||
# sa.Column('cover_image_id', sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['author_id'], ['user.id'], ),
|
sa.ForeignKeyConstraint(['author_id'], ['user.id'], ),
|
||||||
# sa.ForeignKeyConstraint(['cover_image_id'], ['package_screenshot.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['license_id'], ['license.id'], ),
|
sa.ForeignKeyConstraint(['license_id'], ['license.id'], ),
|
||||||
sa.ForeignKeyConstraint(['media_license_id'], ['license.id'], ),
|
sa.ForeignKeyConstraint(['media_license_id'], ['license.id'], ),
|
||||||
sa.ForeignKeyConstraint(['review_thread_id'], ['thread.id'], ),
|
sa.ForeignKeyConstraint(['review_thread_id'], ['thread.id'], ),
|
||||||
|
|
|
@ -7,6 +7,7 @@ beautifulsoup4==4.10.0
|
||||||
billiard==3.6.4.0
|
billiard==3.6.4.0
|
||||||
bleach==4.1.0
|
bleach==4.1.0
|
||||||
blinker==1.4
|
blinker==1.4
|
||||||
|
git+https://github.com/lepture/captcha.git@2792068
|
||||||
celery==5.2.3
|
celery==5.2.3
|
||||||
certifi==2021.10.8
|
certifi==2021.10.8
|
||||||
cffi==1.15.0
|
cffi==1.15.0
|
||||||
|
|
|
@ -18,6 +18,7 @@ passlib
|
||||||
pygments
|
pygments
|
||||||
|
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
captcha
|
||||||
celery
|
celery
|
||||||
kombu
|
kombu
|
||||||
GitPython
|
GitPython
|
||||||
|
|
|
@ -776,18 +776,18 @@ msgstr "Sie befinden sich auf dem %(place)s. Platz."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "Top %(type)s"
|
msgstr "Top projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "Top %(group)d %(type)s"
|
msgstr "Top %(group)d projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s hat ein %(type)s auf dem %(place)d. Platz."
|
msgstr "%(display_name)s hat ein projects auf dem %(place)d. Platz."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1054,8 +1054,8 @@ msgstr "Themen"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Suche %(type)s"
|
msgstr "Suche projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1386,8 +1386,8 @@ msgstr "Verwalten Sie Ihre Einstellungen"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Dies ist eine „%(type)s“-Benachrichtigung."
|
msgstr "Dies ist eine „projects“-Benachrichtigung."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1665,8 +1665,8 @@ msgstr "Rezension"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "Empfehlen Sie dieses %(type)s?"
|
msgstr "Empfehlen Sie dieses projects?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1898,10 +1898,10 @@ msgstr "Weiterlesen"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Sie können eine .cdb.json-Datei in Ihre %(type)s einfügen, um diese "
|
"Sie können eine .cdb.json-Datei in Ihre projects einfügen, um diese "
|
||||||
"Details automatisch zu aktualisieren."
|
"Details automatisch zu aktualisieren."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -774,18 +774,18 @@ msgstr "Estás en el lugar %(place)s."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s tiene un %(type)s en el puesto #%(place)d."
|
msgstr "%(display_name)s tiene un projects en el puesto #%(place)d."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1044,8 +1044,8 @@ msgstr "Hilos de discusión"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Buscar %(type)s"
|
msgstr "Buscar projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1376,8 +1376,8 @@ msgstr "Administrar tus preferencias"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Esta es una notificación de %(type)s."
|
msgstr "Esta es una notificación de projects."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1639,8 +1639,8 @@ msgstr "Reseñar"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "¿Recomienda este %(type)s?"
|
msgstr "¿Recomienda este projects?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1870,10 +1870,10 @@ msgstr "Leer más"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Puede incluir un archivo .cdb.json en su %(type)s para actualizar estos "
|
"Puede incluir un archivo .cdb.json en su projects para actualizar estos "
|
||||||
"detalles automáticamente."
|
"detalles automáticamente."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -776,18 +776,18 @@ msgstr "Vous êtes à la %(place)s. place."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "Top %(type)s"
|
msgstr "Top projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "Top %(group)d %(type)s"
|
msgstr "Top %(group)d projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s a un %(type)s à la #%(place)d place."
|
msgstr "%(display_name)s a un projects à la #%(place)d place."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1049,8 +1049,8 @@ msgstr "Fils"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Rechercher %(type)s"
|
msgstr "Rechercher projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1383,8 +1383,8 @@ msgstr "Gérez vos préférences"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Il s'agit d'une notification « %(type)s »."
|
msgstr "Il s'agit d'une notification « projects »."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1650,8 +1650,8 @@ msgstr "Évaluation"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "Recommandez-vous ce %(type)s ?"
|
msgstr "Recommandez-vous ce projects ?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1884,10 +1884,10 @@ msgstr "Lire plus"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous pouvez inclure un fichier .cdb.json dans votre %(type)s pour mettre "
|
"Vous pouvez inclure un fichier .cdb.json dans votre projects pour mettre "
|
||||||
"à jour ces détails automatiquement."
|
"à jour ces détails automatiquement."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -786,17 +786,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1053,7 +1053,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1377,7 +1377,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1624,7 +1624,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1851,7 +1851,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -768,18 +768,18 @@ msgstr "Anda berada pada urutan %(place)s."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "%(type)s teratas"
|
msgstr "projects teratas"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "%(group)d %(type)s teratas"
|
msgstr "%(group)d projects teratas"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s memiliki sebuah %(type)s yang ada di urutan ke-%(place)d."
|
msgstr "%(display_name)s memiliki sebuah projects yang ada di urutan ke-%(place)d."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1043,8 +1043,8 @@ msgstr "Utas"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Cari %(type)s"
|
msgstr "Cari projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1373,8 +1373,8 @@ msgstr "Kelola pilihan Anda"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Ini adalah pemberitahuan '%(type)s'."
|
msgstr "Ini adalah pemberitahuan 'projects'."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1636,8 +1636,8 @@ msgstr "Ulasan"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "Apa Anda menyarankan %(type)s ini?"
|
msgstr "Apa Anda menyarankan projects ini?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1865,10 +1865,10 @@ msgstr "Baca lebih banyak"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Anda dapat menyisipkan berkas .cdb.json dalam %(type)s Anda untuk "
|
"Anda dapat menyisipkan berkas .cdb.json dalam projects Anda untuk "
|
||||||
"memperbarui detail ini secara otomatis."
|
"memperbarui detail ini secara otomatis."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -748,17 +748,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1008,7 +1008,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1332,7 +1332,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1579,7 +1579,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1806,7 +1806,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -748,17 +748,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1008,7 +1008,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1332,7 +1332,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1579,7 +1579,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1806,7 +1806,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -774,18 +774,18 @@ msgstr "Anda berada di kedudukan %(place)s."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "%(type)s teratas"
|
msgstr "projects teratas"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "%(type)s %(group)d teratas"
|
msgstr "projects %(group)d teratas"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s mempunyai suatu %(type)s berkedudukan #%(place)d."
|
msgstr "%(display_name)s mempunyai suatu projects berkedudukan #%(place)d."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1049,8 +1049,8 @@ msgstr "Bebenang"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Cari %(type)s"
|
msgstr "Cari projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1381,8 +1381,8 @@ msgstr "Uruskan keutamaan anda"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Ini pemberitahuan '%(type)s'."
|
msgstr "Ini pemberitahuan 'projects'."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1648,8 +1648,8 @@ msgstr "Ulasan"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "Adakah anda mengesyorkan %(type)s ini?"
|
msgstr "Adakah anda mengesyorkan projects ini?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1879,10 +1879,10 @@ msgstr "Baca lanjut"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Anda boleh sertakan fail .cdb.json dalam %(type)s anda untuk kemas kini "
|
"Anda boleh sertakan fail .cdb.json dalam projects anda untuk kemas kini "
|
||||||
"maklumat ini secara automatiknya."
|
"maklumat ini secara automatiknya."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -750,17 +750,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1011,7 +1011,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1335,7 +1335,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1582,7 +1582,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1809,7 +1809,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -770,18 +770,18 @@ msgstr "Вы на %(place)s месте."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "Топ %(type)s"
|
msgstr "Топ projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "Топ %(group)d %(type)s"
|
msgstr "Топ %(group)d projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s имеет %(type)s на #%(place)d."
|
msgstr "%(display_name)s имеет projects на #%(place)d."
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1045,8 +1045,8 @@ msgstr "Треды"
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr "Искать %(type)s"
|
msgstr "Искать projects"
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
#: app/templates/todo/tags.html:13
|
#: app/templates/todo/tags.html:13
|
||||||
|
@ -1382,8 +1382,8 @@ msgstr "Управляйте своими предпочтениями"
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr "Это '%(type)s' уведомление."
|
msgstr "Это 'projects' уведомление."
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
#: app/templates/emails/notification_digest.html:29
|
#: app/templates/emails/notification_digest.html:29
|
||||||
|
@ -1655,8 +1655,8 @@ msgstr "Обзор"
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr "Рекомендуете ли вы этот %(type)s?"
|
msgstr "Рекомендуете ли вы этот projects?"
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
#: app/templates/packages/review_create_edit.html:40
|
#: app/templates/packages/review_create_edit.html:40
|
||||||
|
@ -1888,10 +1888,10 @@ msgstr "Читать далее"
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Вы можете включить файл .cdb.json в свой %(type)s для автоматического "
|
"Вы можете включить файл .cdb.json в свой projects для автоматического "
|
||||||
"обновления этих данных."
|
"обновления этих данных."
|
||||||
|
|
||||||
#: app/templates/packages/create_edit.html:55
|
#: app/templates/packages/create_edit.html:55
|
||||||
|
|
|
@ -750,17 +750,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1010,7 +1010,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1334,7 +1334,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1581,7 +1581,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1808,7 +1808,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -754,17 +754,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1014,7 +1014,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1338,7 +1338,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1585,7 +1585,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1812,7 +1812,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -752,18 +752,18 @@ msgstr "你在第%(place)s位。"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr "最高%(type)s"
|
msgstr "最高projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr "最高 %(group)d %(type)s"
|
msgstr "最高 %(group)d projects"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr "%(display_name)s 有一个 %(type)s 放置在 #%(place)d 处。"
|
msgstr "%(display_name)s 有一个 projects 放置在 #%(place)d 处。"
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -1020,7 +1020,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1358,7 +1358,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1610,7 +1610,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1840,7 +1840,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -753,17 +753,17 @@ msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:161
|
#: app/blueprints/users/profile.py:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(type)s"
|
msgid "Top projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:163
|
#: app/blueprints/users/profile.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Top %(group)d %(type)s"
|
msgid "Top %(group)d projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:172
|
#: app/blueprints/users/profile.py:172
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/blueprints/users/profile.py:187
|
#: app/blueprints/users/profile.py:187
|
||||||
|
@ -1014,7 +1014,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48
|
#: app/templates/base.html:48
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Search %(type)s"
|
msgid "Search projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||||
|
@ -1338,7 +1338,7 @@ msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification.html:37
|
#: app/templates/emails/notification.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "This is a '%(type)s' notification."
|
msgid "This is a 'projects' notification."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/emails/notification_digest.html:14
|
#: app/templates/emails/notification_digest.html:14
|
||||||
|
@ -1585,7 +1585,7 @@ msgstr ""
|
||||||
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
#: app/templates/macros/reviews.html:109 app/templates/macros/reviews.html:148
|
||||||
#: app/templates/packages/review_create_edit.html:35
|
#: app/templates/packages/review_create_edit.html:35
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Do you recommend this %(type)s?"
|
msgid "Do you recommend this projects?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: app/templates/macros/reviews.html:124
|
#: app/templates/macros/reviews.html:124
|
||||||
|
@ -1812,7 +1812,7 @@ msgstr ""
|
||||||
#: app/templates/packages/create_edit.html:49
|
#: app/templates/packages/create_edit.html:49
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You can include a .cdb.json file in your %(type)s to update these details"
|
"You can include a .cdb.json file in your projects to update these details"
|
||||||
" automatically."
|
" automatically."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue