Compare commits

..

7 Commits

Author SHA1 Message Date
rubenwardy
7e4eb29db7 Limit releases on package view 2019-03-29 21:01:19 +00:00
rubenwardy
93a74b7681 Fix release auto-approval 2019-03-29 20:52:08 +00:00
rubenwardy
2677e088a8 Fix small style issue on todo page 2019-03-29 20:33:15 +00:00
rubenwardy
0fd4984e5a Redesign todo page, add ability to Approve All screenshots 2019-03-29 20:32:13 +00:00
rubenwardy
896a65fd99 Fix progress bar total 2019-03-29 20:02:10 +00:00
rubenwardy
885209a614 Add unified topic search in QueryBuilder 2019-03-29 19:48:21 +00:00
rubenwardy
4c109d6bd3 Fix release being null in API when release is unapproved
Fixes #129
2019-03-13 14:37:27 +00:00
8 changed files with 192 additions and 117 deletions

View File

@@ -37,7 +37,7 @@ make_searchable(db.metadata)
class ArticleQuery(BaseQuery, SearchQueryMixin):
pass
pass
class UserRank(enum.Enum):
@@ -417,21 +417,23 @@ class Package(db.Model):
for e in PackagePropertyKey:
setattr(self, e.name, getattr(package, e.name))
def getAsDictionaryShort(self, base_url, protonum=None):
def getAsDictionaryShort(self, base_url, version=None, protonum=None):
tnurl = self.getThumbnailURL(1)
release = self.getDownloadRelease(version=version, protonum=protonum)
return {
"name": self.name,
"title": self.title,
"author": self.author.display_name,
"short_description": self.short_desc,
"type": self.type.toName(),
"release": self.getDownloadRelease(protonum).id if self.getDownloadRelease(protonum) is not None else None,
"release": release and release.id,
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
"score": round(self.score * 10) / 10
}
def getAsDictionary(self, base_url, protonum=None):
def getAsDictionary(self, base_url, version=None, protonum=None):
tnurl = self.getThumbnailURL(1)
release = self.getDownloadRelease(version=version, protonum=protonum)
return {
"author": self.author.display_name,
"name": self.name,
@@ -454,7 +456,7 @@ class Package(db.Model):
"screenshots": [base_url + ss.url for ss in self.screenshots],
"url": base_url + self.getDownloadURL(),
"release": self.getDownloadRelease(protonum).id if self.getDownloadRelease(protonum) is not None else None,
"release": release and release.id,
"score": round(self.score * 10) / 10
}
@@ -503,9 +505,8 @@ class Package(db.Model):
return url_for("package_download_page",
author=self.author.username, name=self.name)
def getDownloadRelease(self, protonum=None):
version = None
if protonum is not None:
def getDownloadRelease(self, version=None, protonum=None):
if version is None and protonum is not None:
version = MinetestRelease.query.filter(MinetestRelease.protocol >= int(protonum)).first()
if version is not None:
version = version.id
@@ -514,7 +515,7 @@ class Package(db.Model):
for rel in self.releases:
if rel.approved and (protonum is None or
if rel.approved and (version is None or
((rel.min_rel is None or rel.min_rel_id <= version) and \
(rel.max_rel is None or rel.max_rel_id >= version))):
return rel
@@ -695,6 +696,15 @@ class PackageRelease(db.Model):
def __init__(self):
self.releaseDate = datetime.datetime.now()
def approve(self, user):
if not self.package.checkPerm(user, Permission.APPROVE_RELEASE):
return False
assert(self.task_id is None and self.url is not None and self.url != "")
self.approved = True
return True
class PackageReview(db.Model):
id = db.Column(db.Integer, primary_key=True)

View File

@@ -1,5 +1,5 @@
from .models import db, PackageType, Package, ForumTopic, License, MinetestRelease, PackageRelease
from .utils import isNo
from .utils import isNo, isYes
from sqlalchemy.sql.expression import func
from flask import abort
from sqlalchemy import or_
@@ -28,13 +28,33 @@ class QueryBuilder:
self.lucky = self.random or "lucky" in args
self.hide_nonfree = "nonfree" in hide_flags
self.limit = 1 if self.lucky else None
self.order_by = args.get("sort") or "score"
self.order_by = args.get("sort")
self.order_dir = args.get("order") or "desc"
self.protocol_version = args.get("protocol_version")
self.show_discarded = isYes(args.get("show_discarded"))
self.show_added = args.get("show_added")
if self.show_added is not None:
self.show_added = isYes(self.show_added)
if self.search is not None and self.search.strip() == "":
self.search = None
def setSortIfNone(self, name):
if self.order_by is None:
self.order_by = name
def getMinetestVersion(self):
if not self.protocol_version:
return None
self.protocol_version = int(self.protocol_version)
version = MinetestRelease.query.filter(MinetestRelease.protocol>=self.protocol_version).first()
if version is not None:
return version.id
else:
return 10000000
def buildPackageQuery(self):
query = Package.query.filter_by(soft_deleted=False, approved=True)
@@ -48,7 +68,7 @@ class QueryBuilder:
query = query.order_by(func.random())
else:
to_order = None
if self.order_by == "score":
if self.order_by is None or self.order_by == "score":
to_order = Package.score
elif self.order_by == "created_at":
to_order = Package.created_at
@@ -69,14 +89,9 @@ class QueryBuilder:
query = query.filter(Package.media_license.has(License.is_foss == True))
if self.protocol_version:
self.protocol_version = int(self.protocol_version)
version = MinetestRelease.query.filter(MinetestRelease.protocol>=self.protocol_version).first()
if version is not None:
version = version.id
else:
version = 10000000
version = self.getMinetestVersion()
query = query.join(Package.releases) \
.filter(PackageRelease.approved==True) \
.filter(or_(PackageRelease.min_rel_id==None, PackageRelease.min_rel_id<=version)) \
.filter(or_(PackageRelease.max_rel_id==None, PackageRelease.max_rel_id>=version))
@@ -85,18 +100,31 @@ class QueryBuilder:
return query
def buildTopicQuery(self):
topics = ForumTopic.query \
.filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
.order_by(db.asc(ForumTopic.wip), db.asc(ForumTopic.name), db.asc(ForumTopic.title))
def buildTopicQuery(self, show_added=False):
query = ForumTopic.query
if not self.show_discarded:
query = query.filter_by(discarded=False)
show_added = self.show_added == True or (self.show_added is None and show_added)
if not show_added:
query = query.filter(~ db.exists().where(Package.forums==ForumTopic.topic_id))
if self.order_by is None or self.order_by == "name":
query = query.order_by(db.asc(ForumTopic.wip), db.asc(ForumTopic.name), db.asc(ForumTopic.title))
elif self.order_by == "views":
query = query.order_by(db.desc(ForumTopic.views))
elif self.order_by == "date":
query = query.order_by(db.asc(ForumTopic.created_at))
sort_by = "date"
if self.search:
topics = topics.filter(ForumTopic.title.ilike('%' + self.search + '%'))
query = query.filter(ForumTopic.title.ilike('%' + self.search + '%'))
if len(self.types) > 0:
topics = topics.filter(ForumTopic.type.in_(self.types))
query = query.filter(ForumTopic.type.in_(self.types))
if self.limit:
topics = topics.limit(self.limit)
query = query.limit(self.limit)
return topics
return query

View File

@@ -348,6 +348,7 @@ def makeVCSReleaseFromGithub(id, branch, release, url):
release.url = urlmaker.getCommitDownload(commits[0]["sha"])
release.task_id = None
release.commit_hash = commits[0]["sha"]
release.approve(release.package.author)
print(release.url)
db.session.commit()
@@ -379,6 +380,7 @@ def makeVCSRelease(id, branch):
release.url = "/uploads/" + filename
release.task_id = None
release.commit_hash = repo.head.object.hexsha
release.approve(release.package.author)
print(release.url)
db.session.commit()

View File

@@ -5,53 +5,79 @@
{% endblock %}
{% block content %}
<h2>Awaiting Approval</h2>
<h2 class="mb-4">Approval Queue</h2>
{% if canApproveNew and packages %}
<h3>Packages</h3>
<ul>
{% for p in packages %}
<li><a href="{{ p.getDetailsURL() }}">
{{ p.title }} by {{ p.author.display_name }}
</a></li>
{% else %}
<li><i>No packages need reviewing.</i></ul>
{% endfor %}
</ul>
{% endif %}
<div class="row">
{% if canApproveNew and packages %}
<div class="col-sm-6">
<div class="card">
<h3 class="card-header">Packages</h3>
<div class="list-group list-group-flush">
{% for p in packages %}
<a href="{{ p.getDetailsURL() }}" class="list-group-item list-group-item-action">
{{ p.title }} by {{ p.author.display_name }}
</a>
{% else %}
<li class="list-group-item"><i>No packages need reviewing.</i></li>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if canApproveRel and releases %}
<div class="col-sm-6">
<div class="card">
<h3 class="card-header">Releases</h3>
<ul class="list-group list-group-flush">
{% for r in releases %}
<li class="list-group-item">
<a href="{{ r.getEditURL() }}">{{ r.title }}</a>
on
<a href="{{ r.package.getDetailsURL() }}">
{{ r.package.title }} by {{ r.package.author.display_name }}
</a>
</li>
{% else %}
<li class="list-group-item"><i>No releases need reviewing.</i></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
{% if canApproveScn and screenshots %}
<h3>Screenshots</h3>
<ul>
{% for s in screenshots %}
<li>
<a href="{{ s.getEditURL() }}">{{ s.title }}</a>
on
<a href="{{ s.package.getDetailsURL() }}">
{{ s.package.title }} by {{ s.package.author.display_name }}
</a>
</li>
{% else %}
<li><i>No screenshots need reviewing.</i></ul>
{% endfor %}
</ul>
{% endif %}
{% if canApproveRel and releases %}
<h3>Releases</h3>
<ul>
{% for r in releases %}
<li>
<a href="{{ r.getEditURL() }}">{{ r.title }}</a>
on
<a href="{{ r.package.getDetailsURL() }}">
{{ r.package.title }} by {{ r.package.author.display_name }}
</a>
</li>
{% else %}
<li><i>No releases need reviewing.</i></ul>
{% endfor %}
</ul>
<div class="card my-4">
<h3 class="card-header">Screenshots
<form class="float-right" method="post" action="{{ url_for('todo_page') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="action" value="screenshots_approve_all" />
<input class="btn btn-sm btn-primary" type="submit" value="Approve All" />
</form>
</h3>
<ul class="card-body d-flex p-0 flex-row flex-wrap justify-content-start align-content-start p-4">
{% for s in screenshots %}
<li class="packagetile flex-fill"><a href="{{ s.getEditURL() }}"
style="background-image: url({{ s.getThumbnailURL(3) or '/static/placeholder.png' }});">
<div class="packagegridscrub"></div>
<div class="packagegridinfo">
<h3>
{{ s.title }}
<br />
<small>{{ s.package.title }} by {{ s.package.author.display_name }}</small>
</h3>
<p></p>
</div>
</a></li>
{% else %}
<li><i>No screenshots need reviewing.</i></li>
{% endfor %}
{% for i in range(4) %}
<li class="packagetile flex-fill"></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if not (packages or screenshots or releases) %}
@@ -60,11 +86,19 @@
</p>
{% endif %}
<h2>Unadded Topic List</h2>
<h2 class="mt-4">Unadded Topic List</h2>
<p>
There are
<a href="{{ url_for('todo_topics_page') }}">{{ topics_to_add }} packages</a>
to be added to cdb, based on cdb's forum parser.
{{ total_topics - topics_to_add }} / {{ total_topics }} packages have been been added to cdb,
based on cdb's forum parser. {{ topics_to_add }} remaining.
</p>
<div class="progress my-4">
{% set perc = 32 %}
<div class="progress-bar bg-success" role="progressbar"
style="width: {{ perc }}%" aria-valuenow="{{ perc }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<a class="btn btn-primary" href="{{ url_for('todo_topics_page') }}">View Unadded Topic List</a>
{% endblock %}

View File

@@ -20,8 +20,9 @@ from flask_user import *
import flask_menu as menu
from app import app
from app.models import *
from app.querybuilder import QueryBuilder
@app.route("/todo/")
@app.route("/todo/", methods=["GET", "POST"])
@login_required
def todo_page():
canApproveNew = Permission.APPROVE_NEW.check(current_user)
@@ -40,60 +41,61 @@ def todo_page():
if canApproveScn:
screenshots = PackageScreenshot.query.filter_by(approved=False).all()
if not canApproveNew and not canApproveRel and not canApproveScn:
abort(403)
topics_to_add = ForumTopic.query \
if request.method == "POST":
if request.form["action"] == "screenshots_approve_all":
if not canApproveScn:
abort(403)
PackageScreenshot.query.update({ "approved": True })
db.session.commit()
return redirect(url_for("todo_page"))
else:
abort(400)
topic_query = ForumTopic.query \
.filter_by(discarded=False)
total_topics = topic_query.count()
topics_to_add = topic_query \
.filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
.filter_by(discarded=False) \
.count()
return render_template("todo/list.html", title="Reports and Work Queue",
packages=packages, releases=releases, screenshots=screenshots,
canApproveNew=canApproveNew, canApproveRel=canApproveRel, canApproveScn=canApproveScn,
topics_to_add=topics_to_add)
topics_to_add=topics_to_add, total_topics=total_topics)
@app.route("/todo/topics/")
@login_required
def todo_topics_page():
query = ForumTopic.query
show_discarded = request.args.get("show_discarded") == "True"
if not show_discarded:
query = query.filter_by(discarded=False)
total = query.count()
query = query.filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
sort_by = request.args.get("sort")
if sort_by == "name":
query = query.order_by(db.asc(ForumTopic.wip), db.asc(ForumTopic.name), db.asc(ForumTopic.title))
elif sort_by == "views":
query = query.order_by(db.desc(ForumTopic.views))
elif sort_by is None or sort_by == "date":
query = query.order_by(db.asc(ForumTopic.created_at))
sort_by = "date"
qb = QueryBuilder(request.args)
qb.setSortIfNone("date")
query = qb.buildTopicQuery()
tmp_q = ForumTopic.query
if not qb.show_discarded:
tmp_q = tmp_q.filter_by(discarded=False)
total = tmp_q.count()
topic_count = query.count()
search = request.args.get("q")
if search is not None and search.strip() != "":
query = query.filter(ForumTopic.title.ilike('%' + search + '%'))
page = int(request.args.get("page") or 1)
num = int(request.args.get("n") or 100)
if num > 100 and not current_user.rank.atLeast(UserRank.EDITOR):
num = 100
query = query.paginate(page, num, True)
next_url = url_for("todo_topics_page", page=query.next_num, query=search, \
show_discarded=show_discarded, n=num, sort=sort_by) \
next_url = url_for("todo_topics_page", page=query.next_num, query=qb.search, \
show_discarded=qb.show_discarded, n=num, sort=qb.order_by) \
if query.has_next else None
prev_url = url_for("todo_topics_page", page=query.prev_num, query=search, \
show_discarded=show_discarded, n=num, sort=sort_by) \
prev_url = url_for("todo_topics_page", page=query.prev_num, query=qb.search, \
show_discarded=qb.show_discarded, n=num, sort=qb.order_by) \
if query.has_prev else None
return render_template("todo/topics.html", topics=query.items, total=total, \
topic_count=topic_count, query=search, show_discarded=show_discarded, \
topic_count=topic_count, query=qb.search, show_discarded=qb.show_discarded, \
next_url=next_url, prev_url=prev_url, page=page, page_max=query.pages, \
n=num, sort_by=sort_by)
n=num, sort_by=qb.order_by)

View File

@@ -26,9 +26,10 @@ from app.querybuilder import QueryBuilder
def api_packages_page():
qb = QueryBuilder(request.args)
query = qb.buildPackageQuery()
ver = qb.getMinetestVersion()
pkgs = [package.getAsDictionaryShort(app.config["BASE_URL"], request.args.get("protocol_version")) \
for package in query.all() if package.getDownloadRelease() is not None]
pkgs = [package.getAsDictionaryShort(app.config["BASE_URL"], version=ver) \
for package in query.all()]
return jsonify(pkgs)
@app.route("/api/packages/<author>/<name>/")
@@ -39,10 +40,9 @@ def api_package_page(package):
@app.route("/api/topics/")
def api_topics_page():
query = ForumTopic.query \
.order_by(db.asc(ForumTopic.wip), db.asc(ForumTopic.name), db.asc(ForumTopic.title))
pkgs = [t.getAsDictionary() for t in query.all()]
return jsonify(pkgs)
qb = QueryBuilder(request.args)
query = qb.buildTopicQuery(show_added=True)
return jsonify([t.getAsDictionary() for t in query.all()])
@app.route("/api/topic_discard/", methods=["POST"])

View File

@@ -74,9 +74,9 @@ def packages_page():
def getReleases(package):
if package.checkPerm(current_user, Permission.MAKE_RELEASE):
return package.releases
return package.releases.limit(5)
else:
return [rel for rel in package.releases if rel.approved]
return package.releases.filter_by(approved=True).limit(5)
@app.route("/packages/<author>/<name>/")

View File

@@ -85,7 +85,6 @@ def create_release_page(package):
rel.task_id = uuid()
rel.min_rel = form["min_rel"].data.getActual()
rel.max_rel = form["max_rel"].data.getActual()
rel.approved = package.checkPerm(current_user, Permission.APPROVE_RELEASE)
db.session.add(rel)
db.session.commit()
@@ -105,7 +104,7 @@ def create_release_page(package):
rel.url = uploadedPath
rel.min_rel = form["min_rel"].data.getActual()
rel.max_rel = form["max_rel"].data.getActual()
rel.approved = package.checkPerm(current_user, Permission.APPROVE_RELEASE)
rel.approve(current_user)
db.session.add(rel)
db.session.commit()