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.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
|
||||
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, PackageGameSupport
|
||||
from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
|
||||
NotificationType, PackageUpdateConfig, License, UserRank, PackageGameSupport
|
||||
# from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
|
||||
from app.tasks.importtasks import importRepoScreenshot, checkZipRelease, check_for_updates
|
||||
from app.tasks.appstreamtasks import importFromFlathub
|
||||
from app.utils import addNotification, get_system_user
|
||||
|
@ -90,20 +90,20 @@ def reimport_packages():
|
|||
return redirect(url_for("todo.view_editor"))
|
||||
|
||||
|
||||
@action("Import forum topic list")
|
||||
def import_topic_list():
|
||||
task = importTopicList.delay()
|
||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||
# @action("Import forum topic list")
|
||||
# def import_topic_list():
|
||||
# task = importTopicList.delay()
|
||||
# return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||
|
||||
@action("Import appstream from flathub")
|
||||
def import_from_flathub():
|
||||
task = importFromFlathub.delay()
|
||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("todo.topics")))
|
||||
|
||||
@action("Check all forum accounts")
|
||||
def check_all_forum_accounts():
|
||||
task = checkAllForumAccounts.delay()
|
||||
return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||
# @action("Check all forum accounts")
|
||||
# def check_all_forum_accounts():
|
||||
# task = checkAllForumAccounts.delay()
|
||||
# return redirect(url_for("tasks.check", id=task.id, r=url_for("admin.admin_page")))
|
||||
|
||||
|
||||
@action("Import screenshots")
|
||||
|
@ -297,13 +297,12 @@ def delete_inactive_users():
|
|||
@action("Send Video URL notification")
|
||||
def remind_video_url():
|
||||
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()
|
||||
for user in users:
|
||||
packages = db.session.query(Package.title).filter(
|
||||
or_(Package.author==user, Package.maintainers.any(User.id==user.id)),
|
||||
Package.video_url.is_(None),
|
||||
Package.type == PackageType.GAME,
|
||||
Package.state == PackageState.APPROVED) \
|
||||
.all()
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ from sqlalchemy.sql.expression import func
|
|||
|
||||
from app import csrf
|
||||
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
|
||||
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 .auth import is_api_authd
|
||||
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 = []
|
||||
out[id] = ret
|
||||
|
||||
if package.type != PackageType.TOOL:
|
||||
return
|
||||
|
||||
for dep in package.dependencies:
|
||||
if only_hard and dep.optional:
|
||||
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]
|
||||
|
||||
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:
|
||||
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(
|
||||
func.random()).limit(6).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()
|
||||
pop_gam = query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score)).limit(8).all()
|
||||
pop_txp = query.filter_by(type=PackageType.ASSETPACK).order_by(db.desc(Package.score)).limit(8).all()
|
||||
toplevel_tags = get_toplevel_tags()
|
||||
popular = {}
|
||||
# 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)) \
|
||||
.filter(Package.reviews.any()).limit(4).all()
|
||||
|
||||
|
@ -487,16 +486,15 @@ def homepage():
|
|||
|
||||
def mapPackages(packages: List[Package]):
|
||||
return [pkg.getAsDictionaryShort(current_app.config["BASE_URL"]) for pkg in packages]
|
||||
|
||||
popular = { k:mapPackages(v) for (k, v) in popular.items() }
|
||||
return jsonify({
|
||||
"count": count,
|
||||
"downloads": downloads,
|
||||
"featured": mapPackages(featured),
|
||||
"new": mapPackages(new),
|
||||
"updated": mapPackages(updated),
|
||||
"pop_mod": mapPackages(pop_mod),
|
||||
"pop_txp": mapPackages(pop_txp),
|
||||
"pop_game": mapPackages(pop_gam),
|
||||
"popular": popular,
|
||||
"toplevel": [ x.getAsDictionary() for x in toplevel_tags ],
|
||||
"high_reviewed": mapPackages(high_reviewed)
|
||||
})
|
||||
|
||||
|
@ -505,7 +503,7 @@ def homepage():
|
|||
@cors_allowed
|
||||
def welcome_v1():
|
||||
featured = Package.query \
|
||||
.filter(Package.type == PackageType.GAME, Package.state == PackageState.APPROVED,
|
||||
.filter(Package.state == PackageState.APPROVED,
|
||||
Package.tags.any(name="featured")) \
|
||||
.order_by(func.random()) \
|
||||
.limit(5).all()
|
||||
|
@ -520,23 +518,6 @@ def welcome_v1():
|
|||
"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/")
|
||||
@cors_allowed
|
||||
def all_deps():
|
||||
|
@ -545,7 +526,7 @@ def all_deps():
|
|||
|
||||
def format_pkg(pkg: Package):
|
||||
return {
|
||||
"type": pkg.type.toName(),
|
||||
# "type": pkg.type.toName(),
|
||||
"author": pkg.author.username,
|
||||
"name": pkg.name,
|
||||
"provides": [x.name for x in pkg.provides],
|
||||
|
|
|
@ -3,6 +3,7 @@ from flask import Blueprint, render_template, redirect
|
|||
bp = Blueprint("homepage", __name__)
|
||||
|
||||
from app.models import *
|
||||
from app.utils import get_toplevel_tags
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
|
@ -18,11 +19,12 @@ def home():
|
|||
count = query.count()
|
||||
|
||||
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()
|
||||
pop_mod = join(query.filter_by(type=PackageType.TOOL).order_by(db.desc(Package.score))).limit(8).all()
|
||||
pop_gam = join(query.filter_by(type=PackageType.GAME).order_by(db.desc(Package.score))).limit(8).all()
|
||||
pop_txp = join(query.filter_by(type=PackageType.ASSETPACK).order_by(db.desc(Package.score))).limit(8).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))) \
|
||||
.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()
|
||||
|
||||
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 . import bp
|
||||
from app.utils import is_package_page
|
||||
from ...models import Package, PackageType, PackageState, db, PackageRelease
|
||||
from app.utils import is_package_page, get_toplevel_tags
|
||||
from ...models import Package, PackageState, db, PackageRelease
|
||||
|
||||
|
||||
@bp.route("/packages/<author>/<name>/hub/")
|
||||
@is_package_page
|
||||
def game_hub(package: Package):
|
||||
if package.type != PackageType.GAME:
|
||||
abort(404)
|
||||
|
||||
def join(query):
|
||||
return query.options(
|
||||
joinedload(Package.license),
|
||||
|
@ -37,9 +34,11 @@ def game_hub(package: Package):
|
|||
count = query.count()
|
||||
|
||||
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()
|
||||
pop_gam = join(query.filter_by(type=PackageType.GAME).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()
|
||||
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).order_by(db.desc(Tag.id)).all()
|
||||
popular = {}
|
||||
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))) \
|
||||
.filter(Package.reviews.any()).limit(4).all()
|
||||
|
||||
|
@ -50,5 +49,5 @@ def game_hub(package: Package):
|
|||
updated = updated[:4]
|
||||
|
||||
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)
|
||||
|
|
|
@ -99,10 +99,12 @@ def list_all():
|
|||
|
||||
selected_tags = set(qb.tags)
|
||||
|
||||
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).all()
|
||||
|
||||
return render_template("packages/list.html",
|
||||
query_hint=title, packages=query.items, pagination=query,
|
||||
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):
|
||||
|
@ -120,7 +122,7 @@ def view(package):
|
|||
package.checkPerm(current_user, Permission.APPROVE_NEW))
|
||||
|
||||
conflicting_modnames = None
|
||||
if show_similar and package.type != PackageType.ASSETPACK:
|
||||
if show_similar:
|
||||
conflicting_modnames = db.session.query(MetaPackage.name) \
|
||||
.filter(MetaPackage.id.in_([ mp.id for mp in package.provides ])) \
|
||||
.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])
|
||||
|
||||
packages_uses = None
|
||||
if package.type == PackageType.TOOL:
|
||||
packages_uses = Package.query.filter(
|
||||
Package.type == PackageType.TOOL,
|
||||
Package.id != package.id,
|
||||
Package.state == PackageState.APPROVED,
|
||||
Package.dependencies.any(
|
||||
Dependency.meta_package_id.in_([p.id for p in package.provides]))) \
|
||||
.order_by(db.desc(Package.score)).limit(6).all()
|
||||
packages_uses = Package.query.filter(
|
||||
Package.id != package.id,
|
||||
Package.state == PackageState.APPROVED,
|
||||
Package.dependencies.any(
|
||||
Dependency.meta_package_id.in_([p.id for p in package.provides]))) \
|
||||
.order_by(db.desc(Package.score)).limit(6).all()
|
||||
|
||||
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
|
||||
|
||||
toplevel_tags = get_toplevel_tags() #Tag.query.filter_by(is_toplevel=True).all()
|
||||
|
||||
return render_template("packages/view.html",
|
||||
package=package, releases=releases, packages_uses=packages_uses,
|
||||
conflicting_modnames=conflicting_modnames,
|
||||
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>/")
|
||||
|
@ -226,7 +228,14 @@ def makeLabel(obj):
|
|||
|
||||
|
||||
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)])
|
||||
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)])
|
||||
|
@ -293,9 +302,6 @@ def create_edit(author=None, name=None):
|
|||
form.tags.data = package.tags
|
||||
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():
|
||||
wasNew = False
|
||||
if not package:
|
||||
|
@ -353,7 +359,7 @@ def create_edit(author=None, name=None):
|
|||
form=form, author=author, enable_wizard=enableWizard,
|
||||
packages=package_query.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"])
|
||||
|
@ -408,7 +414,7 @@ def move_to_state(package):
|
|||
def remove(package):
|
||||
if request.method == "GET":
|
||||
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 "?"
|
||||
|
||||
|
@ -501,7 +507,7 @@ def edit_maintainers(package):
|
|||
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,
|
||||
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"])
|
||||
|
@ -540,7 +546,7 @@ def audit(package):
|
|||
|
||||
pagination = query.paginate(page, num, True)
|
||||
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):
|
||||
|
@ -554,7 +560,7 @@ class PackageAliasForm(FlaskForm):
|
|||
@rank_required(UserRank.EDITOR)
|
||||
@is_package_page
|
||||
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"])
|
||||
|
@ -580,7 +586,7 @@ def alias_create_edit(package: Package, alias_id: int = None):
|
|||
|
||||
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/")
|
||||
|
@ -588,7 +594,7 @@ def alias_create_edit(package: Package, alias_id: int = None):
|
|||
@is_package_page
|
||||
def share(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/")
|
||||
|
@ -610,4 +616,4 @@ def similar(package):
|
|||
# .all()
|
||||
|
||||
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):
|
||||
return render_template("packages/releases_list.html",
|
||||
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):
|
||||
|
@ -92,7 +92,7 @@ def create_release(package):
|
|||
except LogicError as e:
|
||||
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/")
|
||||
|
@ -163,7 +163,7 @@ def edit_release(package, id):
|
|||
db.session.commit()
|
||||
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 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"])
|
||||
|
@ -301,7 +301,7 @@ def update_config(package):
|
|||
|
||||
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/")
|
||||
|
@ -314,7 +314,7 @@ def setup_releases(package):
|
|||
if package.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/")
|
||||
|
@ -343,4 +343,4 @@ def bulk_update_config(username=None):
|
|||
Package.update_config.has()) \
|
||||
.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 app.models import db, PackageReview, Thread, ThreadReply, NotificationType, PackageReviewVote, Package, UserRank, \
|
||||
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
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ def list_reviews():
|
|||
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)
|
||||
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):
|
||||
|
@ -123,7 +123,7 @@ def review(package):
|
|||
return redirect(package.getURL("packages.view"))
|
||||
|
||||
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"])
|
||||
|
@ -237,4 +237,4 @@ def review_votes(package):
|
|||
user_biases_info.sort(key=lambda x: -abs(x.balance))
|
||||
|
||||
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()
|
||||
|
||||
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"])
|
||||
|
@ -92,7 +92,7 @@ def create_screenshot(package):
|
|||
except LogicError as e:
|
||||
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"])
|
||||
|
@ -124,7 +124,7 @@ def edit_screenshot(package, id):
|
|||
db.session.commit()
|
||||
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"])
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
import base64
|
||||
import string
|
||||
import random
|
||||
from flask import *
|
||||
from flask_babel import gettext, lazy_gettext, get_locale
|
||||
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 wtforms import *
|
||||
from wtforms.validators import *
|
||||
from captcha.image import ImageCaptcha
|
||||
|
||||
from app.models import *
|
||||
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"))])
|
||||
email = StringField(lazy_gettext("Email"), [InputRequired(), Email()])
|
||||
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()])
|
||||
submit = SubmitField(lazy_gettext("Register"))
|
||||
|
||||
|
||||
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")
|
||||
return
|
||||
|
||||
|
@ -181,7 +184,13 @@ def register():
|
|||
if 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"))
|
||||
|
||||
|
||||
|
|
|
@ -151,24 +151,17 @@ def get_user_medals(user: User) -> Tuple[List[Medal], List[Medal]]:
|
|||
.filter(text("rank <= 30")) \
|
||||
.all()
|
||||
|
||||
user_package_ranks = next(
|
||||
(x for x in user_package_ranks if x[0] == PackageType.TOOL or x[2] <= 10),
|
||||
None)
|
||||
user_package_ranks = next((x for x in user_package_ranks if x[2] <= 10), None)
|
||||
if user_package_ranks:
|
||||
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:
|
||||
title = gettext(u"Top %(type)s", type=top_type.text.lower())
|
||||
title = gettext(u"Top projects", type=top_type.text.lower())
|
||||
else:
|
||||
title = gettext(u"Top %(group)d %(type)s", group=top_rank, type=top_type.text.lower())
|
||||
if top_type == PackageType.TOOL:
|
||||
icon = "fa-box"
|
||||
elif top_type == PackageType.GAME:
|
||||
icon = "fa-gamepad"
|
||||
else:
|
||||
icon = "fa-paint-brush"
|
||||
title = gettext(u"Top %(group)d projects", group=top_rank, type=top_type.text.lower())
|
||||
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)
|
||||
unlocked.append(
|
||||
Medal.make_unlocked(place_to_color(top_rank), icon, title, description))
|
||||
|
|
|
@ -36,11 +36,11 @@ def populate(session):
|
|||
featured.is_protected = True
|
||||
|
||||
# These tags replace "package types"
|
||||
game_tag = Tag("Game")
|
||||
game_tag = Tag("Games")
|
||||
game_tag.is_toplevel = True
|
||||
tool_tag = Tag("Tool")
|
||||
tool_tag = Tag("Tools")
|
||||
tool_tag.is_toplevel = True
|
||||
mod_tag = Tag("Mod")
|
||||
mod_tag = Tag("Mods")
|
||||
mod_tag.is_toplevel = True
|
||||
session.add(featured)
|
||||
session.add(game_tag)
|
||||
|
@ -110,7 +110,7 @@ def populate_test_data(session):
|
|||
tool.title = "Alpha Test"
|
||||
tool.license = licenses["MIT"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = admin_user
|
||||
tool.tags.append(tags["mapgen"])
|
||||
tool.tags.append(tags["environment"])
|
||||
|
@ -134,7 +134,7 @@ def populate_test_data(session):
|
|||
mod1.title = "Awards"
|
||||
mod1.license = licenses["LGPLv2.1"]
|
||||
mod1.media_license = licenses["MIT"]
|
||||
mod1.type = PackageType.TOOL
|
||||
# mod1.type = PackageType.TOOL
|
||||
mod1.author = admin_user
|
||||
mod1.tags.append(tags["player_effects"])
|
||||
mod1.repo = "https://github.com/libregaming/awards"
|
||||
|
@ -170,7 +170,7 @@ awards.register_achievement("award_mesefind",{
|
|||
mod2.name = "mesecons"
|
||||
mod2.title = "Mesecons"
|
||||
mod2.tags.append(tags["tools"])
|
||||
mod2.type = PackageType.TOOL
|
||||
# mod2.type = PackageType.TOOL
|
||||
mod2.license = licenses["LGPLv3"]
|
||||
mod2.media_license = licenses["MIT"]
|
||||
mod2.author = jeija
|
||||
|
@ -260,7 +260,7 @@ No warranty is provided, express or implied, for any part of the project.
|
|||
tool.title = "Handholds"
|
||||
tool.license = licenses["MIT"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = ez
|
||||
tool.tags.append(tags["player_effects"])
|
||||
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.license = licenses["MIT"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = ez
|
||||
tool.tags.append(tags["mapgen"])
|
||||
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.license = licenses["LGPLv2.1"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = admin_user
|
||||
tool.tags.append(tags["player_effects"])
|
||||
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.license = licenses["CC0"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = admin_user
|
||||
tool.tags.append(tags["player_effects"])
|
||||
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.name = "capturetheflag"
|
||||
game1.title = "Capture The Flag"
|
||||
game1.type = PackageType.GAME
|
||||
# game1.type = PackageType.GAME
|
||||
game1.license = licenses["LGPLv2.1"]
|
||||
game1.media_license = licenses["MIT"]
|
||||
game1.author = admin_user
|
||||
|
@ -397,7 +397,7 @@ Uses the CTF PvP Engine.
|
|||
tool.title = "PixelBOX Reloaded"
|
||||
tool.license = licenses["CC0"]
|
||||
tool.media_license = licenses["MIT"]
|
||||
tool.type = PackageType.ASSETPACK
|
||||
# tool.type = PackageType.ASSETPACK
|
||||
tool.author = admin_user
|
||||
tool.forums = 14132
|
||||
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()
|
||||
|
||||
metas = {}
|
||||
for package in Package.query.filter_by(type=PackageType.TOOL).all():
|
||||
meta = None
|
||||
try:
|
||||
meta = metas[package.name]
|
||||
except KeyError:
|
||||
meta = MetaPackage(package.name)
|
||||
session.add(meta)
|
||||
metas[package.name] = meta
|
||||
package.provides.append(meta)
|
||||
# metas = {}
|
||||
# for package in Package.query.filter_by(type=PackageType.TOOL).all():
|
||||
# meta = None
|
||||
# try:
|
||||
# meta = metas[package.name]
|
||||
# except KeyError:
|
||||
# meta = MetaPackage(package.name)
|
||||
# session.add(meta)
|
||||
# metas[package.name] = meta
|
||||
# package.provides.append(meta)
|
||||
|
||||
dep = Dependency(food_sweet, meta=metas["food"])
|
||||
session.add(dep)
|
||||
# dep = Dependency(food_sweet, meta=metas["food"])
|
||||
# session.add(dep)
|
||||
|
|
|
@ -390,9 +390,8 @@ Supported query parameters:
|
|||
* `downloads`: get number of downloads
|
||||
* `new`: new packages
|
||||
* `updated`: recently updated packages
|
||||
* `pop_mod`: popular mods
|
||||
* `pop_txp`: popular textures
|
||||
* `pop_game`: popular games
|
||||
* `popular`: popular packages
|
||||
* `toplevel`: toplevel nav tags
|
||||
* `high_reviewed`: highest reviewed
|
||||
* GET `/api/welcome/v1/` ([View](/api/welcome/v1/)) - in-menu welcome dialog. Experimental (may change without warning)
|
||||
* `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.
|
||||
|
||||
<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
|
||||
|
|
|
@ -20,7 +20,7 @@ import sys
|
|||
from typing import List, Dict, Optional, Iterator, Iterable
|
||||
|
||||
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):
|
||||
|
@ -128,39 +128,36 @@ class GameSupportResolver:
|
|||
history = history.copy()
|
||||
history.append(key)
|
||||
|
||||
if package.type == PackageType.GAME:
|
||||
return PackageSet([package])
|
||||
# if package.type == PackageType.GAME:
|
||||
return PackageSet([package])
|
||||
|
||||
if key in self.resolved_packages:
|
||||
return self.resolved_packages.get(key)
|
||||
# if key in self.resolved_packages:
|
||||
# return self.resolved_packages.get(key)
|
||||
|
||||
if key in self.checked_packages:
|
||||
print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
||||
return PackageSet()
|
||||
# if key in self.checked_packages:
|
||||
# print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
||||
# return PackageSet()
|
||||
|
||||
self.checked_packages.add(key)
|
||||
# self.checked_packages.add(key)
|
||||
|
||||
if package.type != PackageType.TOOL:
|
||||
raise LogicError(500, "Got non-tool")
|
||||
# retval = PackageSet()
|
||||
|
||||
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():
|
||||
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")
|
||||
|
||||
self.resolved_packages[key] = retval
|
||||
return retval
|
||||
# self.resolved_packages[key] = retval
|
||||
# return retval
|
||||
|
||||
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, [])
|
||||
for game in retval:
|
||||
support = PackageGameSupport(package, game)
|
||||
|
|
|
@ -20,7 +20,7 @@ import validators
|
|||
from flask_babel import lazy_gettext
|
||||
|
||||
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
|
||||
from app.utils import addAuditLog
|
||||
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)
|
||||
|
||||
if "type" in data:
|
||||
data["type"] = PackageType.coerce(data["type"])
|
||||
# if "type" in data:
|
||||
# data["type"] = PackageType.coerce(data["type"])
|
||||
|
||||
if "dev_state" in data:
|
||||
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:
|
||||
setattr(package, key, data[key])
|
||||
|
||||
if package.type == PackageType.ASSETPACK:
|
||||
package.license = package.media_license
|
||||
# if package.type == PackageType.ASSETPACK:
|
||||
# package.license = package.media_license
|
||||
|
||||
if was_new and package.type == PackageType.TOOL:
|
||||
m = MetaPackage.GetOrCreate(package.name, {})
|
||||
package.provides.append(m)
|
||||
# if was_new and package.type == PackageType.TOOL:
|
||||
# m = MetaPackage.GetOrCreate(package.name, {})
|
||||
# package.provides.append(m)
|
||||
|
||||
if "tags" in data:
|
||||
old_tags = list(package.tags)
|
||||
|
|
|
@ -120,7 +120,7 @@ class ForumTopic(db.Model):
|
|||
wip = 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)
|
||||
name = db.Column(db.String(30), nullable=True)
|
||||
link = db.Column(db.String(200), nullable=True)
|
||||
|
|
|
@ -48,49 +48,49 @@ class License(db.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class PackageType(enum.Enum):
|
||||
GAME = "Game"
|
||||
TOOL = "Tool"
|
||||
ASSETPACK = "Asset Pack"
|
||||
# class PackageType(enum.Enum):
|
||||
# GAME = "Game"
|
||||
# TOOL = "Tool"
|
||||
# ASSETPACK = "Asset Pack"
|
||||
|
||||
def toName(self):
|
||||
return self.name.lower()
|
||||
# def toName(self):
|
||||
# return self.name.lower()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
# def __str__(self):
|
||||
# return self.name
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
if self == PackageType.TOOL:
|
||||
return lazy_gettext("Tool")
|
||||
elif self == PackageType.GAME:
|
||||
return lazy_gettext("Game")
|
||||
elif self == PackageType.ASSETPACK:
|
||||
return lazy_gettext("Asset Pack")
|
||||
# @property
|
||||
# def text(self):
|
||||
# if self == PackageType.TOOL:
|
||||
# return lazy_gettext("Tool")
|
||||
# elif self == PackageType.GAME:
|
||||
# return lazy_gettext("Game")
|
||||
# elif self == PackageType.ASSETPACK:
|
||||
# return lazy_gettext("Asset Pack")
|
||||
|
||||
@property
|
||||
def plural(self):
|
||||
if self == PackageType.TOOL:
|
||||
return lazy_gettext("Tools")
|
||||
elif self == PackageType.GAME:
|
||||
return lazy_gettext("Games")
|
||||
elif self == PackageType.ASSETPACK:
|
||||
return lazy_gettext("Asset Packs")
|
||||
# @property
|
||||
# def plural(self):
|
||||
# if self == PackageType.TOOL:
|
||||
# return lazy_gettext("Tools")
|
||||
# elif self == PackageType.GAME:
|
||||
# return lazy_gettext("Games")
|
||||
# elif self == PackageType.ASSETPACK:
|
||||
# return lazy_gettext("Asset Packs")
|
||||
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
try:
|
||||
return PackageType[name.upper()]
|
||||
except KeyError:
|
||||
return None
|
||||
# @classmethod
|
||||
# def get(cls, name):
|
||||
# try:
|
||||
# return PackageType[name.upper()]
|
||||
# except KeyError:
|
||||
# return None
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
return [(choice, choice.text) for choice in cls]
|
||||
# @classmethod
|
||||
# def choices(cls):
|
||||
# return [(choice, choice.text) for choice in cls]
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, item):
|
||||
return item if type(item) == PackageType else PackageType[item.upper()]
|
||||
# @classmethod
|
||||
# def coerce(cls, item):
|
||||
# return item if type(item) == PackageType else PackageType[item.upper()]
|
||||
|
||||
|
||||
class PackageDevState(enum.Enum):
|
||||
|
@ -218,7 +218,7 @@ class PackagePropertyKey(enum.Enum):
|
|||
title = "Title"
|
||||
short_desc = "Short Description"
|
||||
desc = "Description"
|
||||
type = "Type"
|
||||
# type = "Type"
|
||||
license = "License"
|
||||
media_license = "Media License"
|
||||
tags = "Tags"
|
||||
|
@ -378,7 +378,7 @@ class Package(db.Model):
|
|||
desc = db.Column(db.UnicodeText, nullable=True)
|
||||
build_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)
|
||||
approved_at = db.Column(db.DateTime, nullable=True, default=None)
|
||||
|
||||
|
@ -534,7 +534,7 @@ class Package(db.Model):
|
|||
"title": self.title,
|
||||
"author": self.author.username,
|
||||
"short_description": short_desc,
|
||||
"type": self.type.toName(),
|
||||
# "type": self.type.toName(),
|
||||
"release": release_id,
|
||||
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
|
||||
"aliases": [ alias.getAsDictionary() for alias in self.aliases ],
|
||||
|
@ -559,7 +559,7 @@ class Package(db.Model):
|
|||
"title": self.title,
|
||||
"short_description": self.short_desc,
|
||||
"long_description": self.desc,
|
||||
"type": self.type.toName(),
|
||||
# "type": self.type.toName(),
|
||||
"created_at": self.created_at.isoformat(),
|
||||
|
||||
"license": self.license.name,
|
||||
|
@ -771,7 +771,7 @@ class MetaPackage(db.Model):
|
|||
dependencies = db.relationship("Dependency", back_populates="meta_package", lazy="dynamic")
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
|
||||
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
|
||||
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,
|
||||
"downloads": self.downloads,
|
||||
"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):
|
||||
|
@ -963,8 +914,6 @@ class PackageRelease(db.Model):
|
|||
"release_date": self.releaseDate.isoformat(),
|
||||
"commit": self.commit_hash,
|
||||
"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,
|
||||
"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.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
|
||||
from .utils import isYes, get_int_or_abort
|
||||
|
||||
|
@ -17,11 +17,11 @@ class QueryBuilder:
|
|||
title = "Packages"
|
||||
|
||||
# Get request types
|
||||
types = args.getlist("type")
|
||||
types = [PackageType.get(tname) for tname in types]
|
||||
types = [type for type in types if type is not None]
|
||||
if len(types) > 0:
|
||||
title = ", ".join([str(type.plural) for type in types])
|
||||
# types = args.getlist("type")
|
||||
# types = [PackageType.get(tname) for tname in types]
|
||||
# types = [type for type in types if type is not None]
|
||||
# if len(types) > 0:
|
||||
# title = ", ".join([str(type.plural) for type in types])
|
||||
|
||||
# Get tags types
|
||||
tags = args.getlist("tag")
|
||||
|
@ -32,7 +32,7 @@ class QueryBuilder:
|
|||
self.hide_flags = set(args.getlist("hide"))
|
||||
|
||||
self.title = title
|
||||
self.types = types
|
||||
# self.types = types
|
||||
self.tags = tags
|
||||
|
||||
self.random = "random" in args
|
||||
|
@ -126,8 +126,8 @@ class QueryBuilder:
|
|||
return query
|
||||
|
||||
def filterPackageQuery(self, query):
|
||||
if len(self.types) > 0:
|
||||
query = query.filter(Package.type.in_(self.types))
|
||||
# if len(self.types) > 0:
|
||||
# query = query.filter(Package.type.in_(self.types))
|
||||
|
||||
if self.author:
|
||||
author = User.query.filter_by(username=self.author).first()
|
||||
|
@ -233,8 +233,8 @@ class QueryBuilder:
|
|||
query = query.filter(or_(ForumTopic.title.ilike('%' + self.search + '%'),
|
||||
ForumTopic.name == self.search.lower()))
|
||||
|
||||
if len(self.types) > 0:
|
||||
query = query.filter(ForumTopic.type.in_(self.types))
|
||||
# if len(self.types) > 0:
|
||||
# query = query.filter(ForumTopic.type.in_(self.types))
|
||||
|
||||
if 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 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 :|
|
||||
def get_urls(app):
|
||||
kinds = [AppStreamGlib.UrlKind(kind) for kind in range(11)]
|
||||
|
@ -80,23 +90,39 @@ def importFromFlathub():
|
|||
game1.state = PackageState.APPROVED
|
||||
game1.name = app.get_id()
|
||||
game1.title = app.get_name()
|
||||
if "Development" in app.get_categories():
|
||||
game1.type = PackageType.TOOL
|
||||
else:
|
||||
game1.type = PackageType.GAME
|
||||
hashtags = []
|
||||
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:
|
||||
row = License(license)
|
||||
licenses[row.name] = row
|
||||
session.add(row)
|
||||
session.commit()
|
||||
for category in app.get_categories():
|
||||
if category.lower() not in tags:
|
||||
row = Tag(category.lower())
|
||||
tags[row.name] = row
|
||||
print("adding tag: ", row.name)
|
||||
session.add(row)
|
||||
game1.tags.append(tags[category.lower()])
|
||||
has_toplevel = False
|
||||
categories = list(set([ x.lower() for x in app.get_categories() ]))
|
||||
added = []
|
||||
# how do we get the type attribute from <component> here?
|
||||
# if app.get_type() == "addon":
|
||||
# game1.tags.append(tags["mods"])
|
||||
# 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
|
||||
if app.get_id() in alwaysAccept:
|
||||
|
@ -110,12 +136,16 @@ def importFromFlathub():
|
|||
for url,t in urls:
|
||||
if t == "bugtracker":
|
||||
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
|
||||
if t == "homepage" and "git" not in url:
|
||||
game1.website = url
|
||||
|
||||
game1.forums = 12835
|
||||
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 += f"\n```\nflatpak install flathub {app.get_id()}\n```\n"
|
||||
game1.install_desc += "Run: \n"
|
||||
|
|
|
@ -117,76 +117,76 @@ def getLinksFromModSearch():
|
|||
|
||||
return links
|
||||
|
||||
@celery.task()
|
||||
def importTopicList():
|
||||
links_by_id = getLinksFromModSearch()
|
||||
# @celery.task()
|
||||
# def importTopicList():
|
||||
# links_by_id = getLinksFromModSearch()
|
||||
|
||||
info_by_id = {}
|
||||
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(15, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': False })
|
||||
getTopicsFromForum(50, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': True })
|
||||
# info_by_id = {}
|
||||
# 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(15, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': False })
|
||||
# getTopicsFromForum(50, out=info_by_id, extra={ 'type': PackageType.GAME, 'wip': True })
|
||||
|
||||
# Caches
|
||||
username_to_user = {}
|
||||
topics_by_id = {}
|
||||
for topic in ForumTopic.query.all():
|
||||
topics_by_id[topic.topic_id] = topic
|
||||
# # Caches
|
||||
# username_to_user = {}
|
||||
# topics_by_id = {}
|
||||
# for topic in ForumTopic.query.all():
|
||||
# topics_by_id[topic.topic_id] = topic
|
||||
|
||||
def get_or_create_user(username):
|
||||
user = username_to_user.get(username)
|
||||
if user:
|
||||
return user
|
||||
# def get_or_create_user(username):
|
||||
# user = username_to_user.get(username)
|
||||
# if user:
|
||||
# return user
|
||||
|
||||
if not is_username_valid(username):
|
||||
return None
|
||||
# if not is_username_valid(username):
|
||||
# return None
|
||||
|
||||
user = User.query.filter_by(forums_username=username).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user:
|
||||
return None
|
||||
# user = User.query.filter_by(forums_username=username).first()
|
||||
# if user is None:
|
||||
# user = User.query.filter_by(username=username).first()
|
||||
# if user:
|
||||
# return None
|
||||
|
||||
user = User(username)
|
||||
user.forums_username = username
|
||||
db.session.add(user)
|
||||
# user = User(username)
|
||||
# user.forums_username = username
|
||||
# db.session.add(user)
|
||||
|
||||
username_to_user[username] = user
|
||||
return user
|
||||
# username_to_user[username] = user
|
||||
# return user
|
||||
|
||||
# Create or update
|
||||
for info in info_by_id.values():
|
||||
id = int(info["id"])
|
||||
# # Create or update
|
||||
# for info in info_by_id.values():
|
||||
# id = int(info["id"])
|
||||
|
||||
# Get author
|
||||
username = info["author"]
|
||||
user = get_or_create_user(username)
|
||||
if user is None:
|
||||
print("Error! Unable to create user {}".format(username), file=sys.stderr)
|
||||
continue
|
||||
# # Get author
|
||||
# username = info["author"]
|
||||
# user = get_or_create_user(username)
|
||||
# if user is None:
|
||||
# print("Error! Unable to create user {}".format(username), file=sys.stderr)
|
||||
# continue
|
||||
|
||||
# Get / add row
|
||||
topic = topics_by_id.get(id)
|
||||
if topic is None:
|
||||
topic = ForumTopic()
|
||||
db.session.add(topic)
|
||||
# # Get / add row
|
||||
# topic = topics_by_id.get(id)
|
||||
# if topic is None:
|
||||
# topic = ForumTopic()
|
||||
# db.session.add(topic)
|
||||
|
||||
# Parse title
|
||||
title, name = parseTitle(info["title"])
|
||||
# # Parse title
|
||||
# title, name = parseTitle(info["title"])
|
||||
|
||||
# Get link
|
||||
link = links_by_id.get(id)
|
||||
# # Get link
|
||||
# link = links_by_id.get(id)
|
||||
|
||||
# Fill row
|
||||
topic.topic_id = int(id)
|
||||
topic.author = user
|
||||
topic.type = info["type"]
|
||||
topic.title = title
|
||||
topic.name = name
|
||||
topic.link = link
|
||||
topic.wip = info["wip"]
|
||||
topic.posts = int(info["posts"])
|
||||
topic.views = int(info["views"])
|
||||
topic.created_at = info["date"]
|
||||
# # Fill row
|
||||
# topic.topic_id = int(id)
|
||||
# topic.author = user
|
||||
# topic.type = info["type"]
|
||||
# topic.title = title
|
||||
# topic.name = name
|
||||
# topic.link = link
|
||||
# topic.wip = info["wip"]
|
||||
# topic.posts = int(info["posts"])
|
||||
# topic.views = int(info["views"])
|
||||
# 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):
|
||||
try:
|
||||
tree = build_tree(path, expected_type=ContentType[release.package.type.name],
|
||||
author=release.package.author.username, name=release.package.name)
|
||||
tree = build_tree(path, author=release.package.author.username, name=release.package.name)
|
||||
|
||||
|
||||
cache = {}
|
||||
|
@ -109,9 +108,9 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
|
|||
db.session.add(Dependency(package, meta=meta, optional=True))
|
||||
|
||||
# Update game supports
|
||||
if package.type == PackageType.TOOL:
|
||||
resolver = GameSupportResolver()
|
||||
resolver.update(package)
|
||||
# if package.type == PackageType.TOOL:
|
||||
# resolver = GameSupportResolver()
|
||||
# resolver.update(package)
|
||||
|
||||
# # Update min/max
|
||||
# if tree.meta.get("min_minetest_version"):
|
||||
|
|
|
@ -6,32 +6,29 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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/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="shortcut icon" href="/favicon-16.png" sizes="16x16">
|
||||
<link rel="icon" href="/favicon-128.png" sizes="128x128">
|
||||
<link rel="icon" href="/favicon-32.png" sizes="32x32">
|
||||
<link rel="icon" href="/static/lg-logo.png" sizes="290x290">
|
||||
{% block headextra %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<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">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarColor01">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{% for tag in toplevel %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('packages.list_all', tag='game') }}">{{ _("Games") }}</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>
|
||||
<a class="nav-link" href="{{ url_for('packages.list_all', tag=tag.name) }}">{{ _(tag.title) }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('packages.list_all', random=1, lucky=1) }}">{{ _("Random") }}</a>
|
||||
</li>
|
||||
|
@ -45,7 +42,7 @@
|
|||
<form class="form-inline my-2 my-lg-0" method="GET" action="/packages/">
|
||||
{% if type %}<input type="hidden" name="type" value="{{ type }}" />{% endif %}
|
||||
<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 ''}}">
|
||||
<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"
|
||||
|
|
|
@ -34,5 +34,5 @@
|
|||
{{ _("Unsubscribe") }}
|
||||
</a> <br>
|
||||
|
||||
{{ _("This is a '%(type)s' notification.", type=notification.type.getTitle()) }}
|
||||
{{ _("This is a 'projects' notification.", type=notification.type.getTitle()) }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -38,17 +38,11 @@
|
|||
alt="{{ _('%(title)s by %(author)s', title=package.title, author=package.author.display_name) }}">
|
||||
</div>
|
||||
<div class="carousel-caption text-shadow">
|
||||
<h3 class="mt-0 mb-3">
|
||||
<strong>{{ package.title }}</strong>
|
||||
</h3>
|
||||
<p>
|
||||
{{ package.short_desc }}
|
||||
</p>
|
||||
{% if package.author %}
|
||||
<div class="d-none d-md-block">
|
||||
<span class="mr-2">
|
||||
{{ package.type.text }}
|
||||
</span>
|
||||
{% for warning in package.content_warnings %}
|
||||
<span class="badge badge-warning" title="{{ warning.description }}">
|
||||
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||
|
@ -106,27 +100,13 @@
|
|||
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
||||
{{ render_pkggrid(updated) }}
|
||||
|
||||
|
||||
<a href="{{ url_for('packages.list_all', type='game', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||
{% for tag in toplevel %}
|
||||
<a href="{{ url_for('packages.list_all', tag=tag.name, sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||
{{ _("See more") }}
|
||||
</a>
|
||||
<h2 class="my-3">{{ _("Top Games") }}</h2>
|
||||
{{ render_pkggrid(pop_gam) }}
|
||||
|
||||
|
||||
<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">{{ _("Top " + tag.title) }}</h2>
|
||||
{{ render_pkggrid(popular[tag.name]) }}
|
||||
{% endfor %}
|
||||
|
||||
<h2 class="my-3">{{ _("Search by Tags") }}</h2>
|
||||
{% for pair in tags %}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
{% endif %}
|
||||
{% 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.") %}
|
||||
|
||||
{% elif package.getMissingHardDependenciesQuery().count() > 0 %}
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
{{ package.short_desc }}
|
||||
</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;">
|
||||
{{ _("<b>Warning:</b> Non-free code and media.") }}
|
||||
</p>
|
||||
{% elif not package.license.is_foss and package.type != package.type.ASSETPACK %}
|
||||
{% elif not package.license.is_foss %}
|
||||
<p style="color:#f33;">
|
||||
{{ _("<b>Warning:</b> Non-free code.") }}
|
||||
</p>
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
<form method="post" action="{{ package.getURL("packages.review") }}" class="card-body">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<p>
|
||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
||||
{{ _("Do you recommend this?") }}
|
||||
</p>
|
||||
|
||||
<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">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<p>
|
||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
||||
{{ _("Do you recommend this?") }}
|
||||
</p>
|
||||
|
||||
<div class="btn-group">
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<div class="alert alert-secondary">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -40,13 +40,13 @@
|
|||
<h2 class="my-3">{{ _("Recently Updated") }}</h2>
|
||||
{{ render_pkggrid(updated) }}
|
||||
|
||||
|
||||
<a href="{{ url_for('packages.list_all', type='tool', sort='score', order='desc', game=package.getId()) }}" class="btn btn-secondary float-right">
|
||||
{% for tag in toplevel %}
|
||||
<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") }}
|
||||
</a>
|
||||
<h2 class="my-3">{{ _("Top Tools") }}</h2>
|
||||
{{ render_pkggrid(pop_mod) }}
|
||||
|
||||
<h2 class="my-3">{{ _("Top " + tag.title) }}</h2>
|
||||
{{ 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">
|
||||
{{ _("See more") }}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{% block author_links %}
|
||||
{% if 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 %}
|
||||
,
|
||||
{% endif %}
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
<h1>{{ self.title() }}</h1>
|
||||
|
||||
<p>
|
||||
{{ _("A release is a single downloadable version of your %(title)s.", title=package.type.text.lower()) }}
|
||||
{{ _("You need to create releases even if you use a rolling release development cycle, as Minetest needs them to check for updates.") }}
|
||||
{{ _("A release is a single downloadable version of your project") }}
|
||||
</p>
|
||||
|
||||
{% if package.repo %}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
{{ _("Do you recommend this %(type)s?", type=package.type.text | lower) }}
|
||||
{{ _("Do you recommend this?") }}
|
||||
</p>
|
||||
{{ render_toggle_field(form.recommends, icons={"yes":"fa-thumbs-up", "no":"fa-thumbs-down"}) }}
|
||||
|
||||
|
|
|
@ -47,9 +47,9 @@
|
|||
{% endblock %}
|
||||
|
||||
{% 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") %}
|
||||
{% elif not package.license.is_foss and package.type != package.type.ASSETPACK %}
|
||||
{% elif not package.license.is_foss %}
|
||||
{% set package_warning=_("Non-free code") %}
|
||||
{% elif not package.media_license.is_foss %}
|
||||
{% set package_warning=_("Non-free media") %}
|
||||
|
@ -230,13 +230,13 @@
|
|||
{% for ss in screenshots %}
|
||||
{% if ss.approved or package.checkPerm(current_user, "ADD_SCREENSHOTS") %}
|
||||
<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 }}" />
|
||||
{% if not ss.approved %}
|
||||
<span class="badge bg-dark badge-tr">{{ _("Awaiting review") }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<li>
|
||||
|
@ -307,12 +307,10 @@
|
|||
{{ render_pkggrid(packages_uses) }}
|
||||
{% endif %}
|
||||
|
||||
{% if package.type == package.type.GAME %}
|
||||
<h2>{{ _("Content") }}</h2>
|
||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-primary">
|
||||
{{ _("View content for game") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
<h2>{{ _("Content") }}</h2>
|
||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-primary">
|
||||
{{ _("View content for game") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<aside class="col-md-3 info-sidebar">
|
||||
|
@ -362,92 +360,81 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if package.type == package.type.GAME %}
|
||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-block mb-4 btn-primary">
|
||||
{{ _("View content for game") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ package.getURL('packages.game_hub') }}" class="btn btn-lg btn-block mb-4 btn-primary">
|
||||
{{ _("View content for game") }}
|
||||
</a>
|
||||
<h3>{{ _("Dependencies") }}</h3>
|
||||
<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 %}
|
||||
<h3>{{ _("Dependencies") }}</h3>
|
||||
<dl>
|
||||
<dt>{{ _("Required") }}</dt>
|
||||
{% set optional_deps=package.getSortedOptionalDependencies() %}
|
||||
{% if optional_deps %}
|
||||
<dt>{{ _("Optional") }}</dt>
|
||||
<dd>
|
||||
{% for dep in package.getSortedHardDependencies() %}
|
||||
{% for dep in optional_deps %}
|
||||
{%- if dep.package %}
|
||||
<a class="badge badge-primary"
|
||||
<a class="badge badge-secondary"
|
||||
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"
|
||||
<a class="badge badge-secondary"
|
||||
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") }}
|
||||
{% endif %}</a>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% set optional_deps=package.getSortedOptionalDependencies() %}
|
||||
{% if optional_deps %}
|
||||
<dt>{{ _("Optional") }}</dt>
|
||||
<dd>
|
||||
{% for dep in optional_deps %}
|
||||
{%- if dep.package %}
|
||||
<a class="badge badge-secondary"
|
||||
href="{{ dep.package.getURL("packages.view") }}">
|
||||
{{ _("%(title)s by %(display_name)s",
|
||||
title=dep.package.title, display_name=dep.package.author.display_name) }}
|
||||
{% elif dep.meta_package %}
|
||||
<a class="badge badge-secondary"
|
||||
href="{{ url_for('metapackages.view', name=dep.meta_package.name) }}">
|
||||
{{ dep.meta_package.name }}
|
||||
{% 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>{{ _("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>
|
||||
|
||||
<h3>
|
||||
{{ _("Information") }}
|
||||
</h3>
|
||||
|
||||
<dl>
|
||||
<dt>{{ _("Type") }}</dt>
|
||||
<dd>{{ package.type.text }}</dd>
|
||||
<dt>{{ _("Technical Name") }}</dt>
|
||||
<dd>{{ package.name }}</dd>
|
||||
<dt>{{ _("License") }}</dt>
|
||||
<dd>
|
||||
{% if package.license == package.media_license %}
|
||||
{{ render_license(package.license) }}
|
||||
{% elif package.type == package.type.ASSETPACK %}
|
||||
{{ render_license(package.media_license) }}
|
||||
{% else %}
|
||||
{{ _("%(code_license)s for code,<br>%(media_license)s for media.",
|
||||
code_license=render_license(package.license), media_license=render_license(package.media_license)) }}
|
||||
|
@ -536,5 +523,25 @@
|
|||
</aside>
|
||||
</div>
|
||||
</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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<i class="fab fa-github mr-1"></i>
|
||||
{{ _("GitHub") }}
|
||||
</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>
|
||||
{{ _("Register") }}
|
||||
</a>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<img src="/static/puzzle.png" />
|
||||
<img src="{{captcha}}" />
|
||||
</p>
|
||||
{{ render_field(form.question, hint=_("Please prove that you are human")) }}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import List, Tuple, Optional
|
||||
|
||||
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 client # noqa
|
||||
|
||||
|
@ -16,30 +16,30 @@ def make_package(name: str, versions: List[Tuple[Optional[str], Optional[str]]])
|
|||
tool.title = name
|
||||
tool.license = license
|
||||
tool.media_license = license
|
||||
tool.type = PackageType.TOOL
|
||||
# tool.type = PackageType.TOOL
|
||||
tool.author = author
|
||||
tool.short_desc = "The content library should not be used yet as it is still in alpha"
|
||||
tool.desc = "This is the long desc"
|
||||
db.session.add(tool)
|
||||
|
||||
rels = []
|
||||
# rels = []
|
||||
|
||||
for (minv, maxv) in versions:
|
||||
rel = PackageRelease()
|
||||
rel.package = tool
|
||||
rel.title = "test"
|
||||
rel.url = "https://github.com/ezhh/handholds/archive/master.zip"
|
||||
# for (minv, maxv) in versions:
|
||||
# rel = PackageRelease()
|
||||
# rel.package = tool
|
||||
# rel.title = "test"
|
||||
# rel.url = "https://github.com/ezhh/handholds/archive/master.zip"
|
||||
|
||||
# if minv:
|
||||
# rel.min_rel = MinetestRelease.query.filter_by(name=minv).first()
|
||||
# assert rel.min_rel
|
||||
# if maxv:
|
||||
# rel.max_rel = MinetestRelease.query.filter_by(name=maxv).first()
|
||||
# assert rel.max_rel
|
||||
# # if minv:
|
||||
# # rel.min_rel = MinetestRelease.query.filter_by(name=minv).first()
|
||||
# # assert rel.min_rel
|
||||
# # if maxv:
|
||||
# # rel.max_rel = MinetestRelease.query.filter_by(name=maxv).first()
|
||||
# # assert rel.max_rel
|
||||
|
||||
rel.approved = True
|
||||
db.session.add(rel)
|
||||
rels.append(rel)
|
||||
# rel.approved = True
|
||||
# db.session.add(rel)
|
||||
# rels.append(rel)
|
||||
|
||||
db.session.flush()
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ from .user import *
|
|||
|
||||
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):
|
||||
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.freeciv.Freeciv',
|
||||
'io.github.EndlessSky.endless-sky',
|
||||
'org.frozen_bubble.frozen-bubble',
|
||||
'org.kde.ksudoku',
|
||||
'net.veloren.veloren'
|
||||
]
|
||||
|
||||
alwaysDeny = [
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
from functools import wraps
|
||||
from flask import abort, redirect, url_for, request
|
||||
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):
|
||||
|
@ -45,7 +45,7 @@ def is_package_page(f):
|
|||
package = getPackageByInfo(author, name)
|
||||
if package is None:
|
||||
package = getPackageByInfo(author, name + "_game")
|
||||
if package and package.type == PackageType.GAME:
|
||||
if package:
|
||||
args = dict(kwargs)
|
||||
args["name"] = name + "_game"
|
||||
return redirect(url_for(request.endpoint, **args))
|
||||
|
|
|
@ -18,6 +18,37 @@ depends_on = None
|
|||
|
||||
|
||||
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! ###
|
||||
op.create_table('content_warning',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
|
@ -96,7 +127,6 @@ def upgrade():
|
|||
sa.Column('desc', sa.UnicodeText(), nullable=True),
|
||||
sa.Column('build_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('approved_at', sa.DateTime(), 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('forums', sa.Integer(), 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(['cover_image_id'], ['package_screenshot.id'], ),
|
||||
sa.ForeignKeyConstraint(['license_id'], ['license.id'], ),
|
||||
sa.ForeignKeyConstraint(['media_license_id'], ['license.id'], ),
|
||||
sa.ForeignKeyConstraint(['review_thread_id'], ['thread.id'], ),
|
||||
|
|
|
@ -7,6 +7,7 @@ beautifulsoup4==4.10.0
|
|||
billiard==3.6.4.0
|
||||
bleach==4.1.0
|
||||
blinker==1.4
|
||||
git+https://github.com/lepture/captcha.git@2792068
|
||||
celery==5.2.3
|
||||
certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
|
|
|
@ -18,6 +18,7 @@ passlib
|
|||
pygments
|
||||
|
||||
beautifulsoup4
|
||||
captcha
|
||||
celery
|
||||
kombu
|
||||
GitPython
|
||||
|
|
|
@ -776,18 +776,18 @@ msgstr "Sie befinden sich auf dem %(place)s. Platz."
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr "Top projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "Top %(group)d projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s hat ein %(type)s auf dem %(place)d. Platz."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s hat ein projects auf dem %(place)d. Platz."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1054,8 +1054,8 @@ msgstr "Themen"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Suche %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Suche projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1386,8 +1386,8 @@ msgstr "Verwalten Sie Ihre Einstellungen"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Dies ist eine „%(type)s“-Benachrichtigung."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Dies ist eine „projects“-Benachrichtigung."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "Empfehlen Sie dieses %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "Empfehlen Sie dieses projects?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1898,10 +1898,10 @@ msgstr "Weiterlesen"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
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."
|
||||
|
||||
#: 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
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s tiene un %(type)s en el puesto #%(place)d."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s tiene un projects en el puesto #%(place)d."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1044,8 +1044,8 @@ msgstr "Hilos de discusión"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Buscar %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Buscar projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1376,8 +1376,8 @@ msgstr "Administrar tus preferencias"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Esta es una notificación de %(type)s."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Esta es una notificación de projects."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "¿Recomienda este %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "¿Recomienda este projects?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1870,10 +1870,10 @@ msgstr "Leer más"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
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."
|
||||
|
||||
#: app/templates/packages/create_edit.html:55
|
||||
|
|
|
@ -776,18 +776,18 @@ msgstr "Vous êtes à la %(place)s. place."
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr "Top projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "Top %(group)d projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s a un %(type)s à la #%(place)d place."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s a un projects à la #%(place)d place."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1049,8 +1049,8 @@ msgstr "Fils"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Rechercher %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Rechercher projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1383,8 +1383,8 @@ msgstr "Gérez vos préférences"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Il s'agit d'une notification « %(type)s »."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Il s'agit d'une notification « projects »."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "Recommandez-vous ce %(type)s ?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "Recommandez-vous ce projects ?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1884,10 +1884,10 @@ msgstr "Lire plus"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
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."
|
||||
|
||||
#: app/templates/packages/create_edit.html:55
|
||||
|
|
|
@ -786,17 +786,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1053,7 +1053,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1377,7 +1377,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1851,7 +1851,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -768,18 +768,18 @@ msgstr "Anda berada pada urutan %(place)s."
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "%(type)s teratas"
|
||||
msgid "Top projects"
|
||||
msgstr "projects teratas"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "%(group)d %(type)s teratas"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "%(group)d projects teratas"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s memiliki sebuah %(type)s yang ada di urutan ke-%(place)d."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s memiliki sebuah projects yang ada di urutan ke-%(place)d."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1043,8 +1043,8 @@ msgstr "Utas"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Cari %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Cari projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1373,8 +1373,8 @@ msgstr "Kelola pilihan Anda"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Ini adalah pemberitahuan '%(type)s'."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Ini adalah pemberitahuan 'projects'."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "Apa Anda menyarankan %(type)s ini?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "Apa Anda menyarankan projects ini?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1865,10 +1865,10 @@ msgstr "Baca lebih banyak"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
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."
|
||||
|
||||
#: app/templates/packages/create_edit.html:55
|
||||
|
|
|
@ -748,17 +748,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1008,7 +1008,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1332,7 +1332,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1806,7 +1806,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -748,17 +748,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1008,7 +1008,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1332,7 +1332,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1806,7 +1806,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -774,18 +774,18 @@ msgstr "Anda berada di kedudukan %(place)s."
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "%(type)s teratas"
|
||||
msgid "Top projects"
|
||||
msgstr "projects teratas"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "%(type)s %(group)d teratas"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "projects %(group)d teratas"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s mempunyai suatu %(type)s berkedudukan #%(place)d."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s mempunyai suatu projects berkedudukan #%(place)d."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1049,8 +1049,8 @@ msgstr "Bebenang"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Cari %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Cari projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1381,8 +1381,8 @@ msgstr "Uruskan keutamaan anda"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Ini pemberitahuan '%(type)s'."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Ini pemberitahuan 'projects'."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "Adakah anda mengesyorkan %(type)s ini?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "Adakah anda mengesyorkan projects ini?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1879,10 +1879,10 @@ msgstr "Baca lanjut"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
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."
|
||||
|
||||
#: app/templates/packages/create_edit.html:55
|
||||
|
|
|
@ -750,17 +750,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1011,7 +1011,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1335,7 +1335,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1809,7 +1809,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -770,18 +770,18 @@ msgstr "Вы на %(place)s месте."
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "Топ %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr "Топ projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "Топ %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "Топ %(group)d projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s имеет %(type)s на #%(place)d."
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s имеет projects на #%(place)d."
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1045,8 +1045,8 @@ msgstr "Треды"
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgstr "Искать %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr "Искать projects"
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
#: app/templates/todo/tags.html:13
|
||||
|
@ -1382,8 +1382,8 @@ msgstr "Управляйте своими предпочтениями"
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgstr "Это '%(type)s' уведомление."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr "Это 'projects' уведомление."
|
||||
|
||||
#: app/templates/emails/notification_digest.html:14
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgstr "Рекомендуете ли вы этот %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr "Рекомендуете ли вы этот projects?"
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
#: app/templates/packages/review_create_edit.html:40
|
||||
|
@ -1888,10 +1888,10 @@ msgstr "Читать далее"
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
"Вы можете включить файл .cdb.json в свой %(type)s для автоматического "
|
||||
"Вы можете включить файл .cdb.json в свой projects для автоматического "
|
||||
"обновления этих данных."
|
||||
|
||||
#: app/templates/packages/create_edit.html:55
|
||||
|
|
|
@ -750,17 +750,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1010,7 +1010,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1334,7 +1334,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1808,7 +1808,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -754,17 +754,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1014,7 +1014,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1338,7 +1338,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1812,7 +1812,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -752,18 +752,18 @@ msgstr "你在第%(place)s位。"
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgstr "最高%(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr "最高projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgstr "最高 %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr "最高 %(group)d projects"
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, python-format
|
||||
msgid "%(display_name)s has a %(type)s placed at #%(place)d."
|
||||
msgstr "%(display_name)s 有一个 %(type)s 放置在 #%(place)d 处。"
|
||||
msgid "%(display_name)s has a projects placed at #%(place)d."
|
||||
msgstr "%(display_name)s 有一个 projects 放置在 #%(place)d 处。"
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
#, python-format
|
||||
|
@ -1020,7 +1020,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1358,7 +1358,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1840,7 +1840,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -753,17 +753,17 @@ msgstr ""
|
|||
|
||||
#: app/blueprints/users/profile.py:161
|
||||
#, python-format
|
||||
msgid "Top %(type)s"
|
||||
msgid "Top projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:163
|
||||
#, python-format
|
||||
msgid "Top %(group)d %(type)s"
|
||||
msgid "Top %(group)d projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/blueprints/users/profile.py:172
|
||||
#, 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 ""
|
||||
|
||||
#: app/blueprints/users/profile.py:187
|
||||
|
@ -1014,7 +1014,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/base.html:48
|
||||
#, python-format
|
||||
msgid "Search %(type)s"
|
||||
msgid "Search projects"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/base.html:48 app/templates/todo/tags.html:11
|
||||
|
@ -1338,7 +1338,7 @@ msgstr ""
|
|||
|
||||
#: app/templates/emails/notification.html:37
|
||||
#, python-format
|
||||
msgid "This is a '%(type)s' notification."
|
||||
msgid "This is a 'projects' notification."
|
||||
msgstr ""
|
||||
|
||||
#: 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/packages/review_create_edit.html:35
|
||||
#, python-format
|
||||
msgid "Do you recommend this %(type)s?"
|
||||
msgid "Do you recommend this projects?"
|
||||
msgstr ""
|
||||
|
||||
#: app/templates/macros/reviews.html:124
|
||||
|
@ -1812,7 +1812,7 @@ msgstr ""
|
|||
#: app/templates/packages/create_edit.html:49
|
||||
#, python-format
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
Loading…
Reference in New Issue