Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b36273a848 | ||
|
|
7b087158d7 | ||
|
|
2fbc44bd54 | ||
|
|
950512c2a7 | ||
|
|
f4010d498f | ||
|
|
f04d4ff3cd | ||
|
|
f8b290fc45 | ||
|
|
7e4eb29db7 | ||
|
|
93a74b7681 | ||
|
|
2677e088a8 | ||
|
|
0fd4984e5a | ||
|
|
896a65fd99 | ||
|
|
885209a614 | ||
|
|
4c109d6bd3 |
@@ -10,36 +10,45 @@ ContentDB is for the community, and as such listings should be useful to the
|
||||
community. To help with this, there are a few rules to improve the quality of
|
||||
the listings and to combat abuse.
|
||||
|
||||
* No inappropriate content.
|
||||
* Content must be playable/useful, but not necessarily finished.
|
||||
* Don't use the name of another mod unless your mod is a fork or reimplementation.
|
||||
* Licenses must allow derivatives, redistribution, and must not discriminate.
|
||||
* Don't put promotions are advertisements in package listings, except for
|
||||
donation and personal website links which are permitted in the long description.
|
||||
* No inappropriate content. <sup>2.1</sup>
|
||||
* Content must be playable/useful, but not necessarily finished. <sup>2.2</sup>
|
||||
* Don't use the name of another mod unless your mod is a fork or reimplementation. <sup>3</sup>
|
||||
* Licenses must allow derivatives, redistribution, and must not discriminate. <sup>4</sup>
|
||||
* Don't put promotions or advertisements in package listings, except for
|
||||
donation and personal website links which are permitted in the
|
||||
long description. <sup>5</sup>
|
||||
* The ContentDB admin reserves the right to remove packages for any reason,
|
||||
including ones not covered by this document, and to ban users who abuse
|
||||
this service. <sup>1</sup>
|
||||
|
||||
|
||||
## 1. General
|
||||
|
||||
It is not permitted to submit abusive, obscene, vulgar, slanderous, hateful,
|
||||
threatening, sexually-orientated or any material that may violate any laws be
|
||||
it of your country, the country where "Content DB” is hosted or International Law.
|
||||
|
||||
The ContentDB admin reserves the right to remove packages for any reason,
|
||||
including ones not covered by this document, and to ban users who abuse this service.
|
||||
|
||||
Also see the [help page on tags](/help/package_tags/).
|
||||
|
||||
|
||||
## 2. Accepted Content and State of Completion
|
||||
## 2. Accepted Content
|
||||
|
||||
### 2.1. Acceptable Content
|
||||
|
||||
Sexually-orientated content is not permitted.
|
||||
|
||||
Mature content, including that relating to drugs, excessive gore, violence, or
|
||||
horror, is not currently permitted - but will be in the future.
|
||||
|
||||
The submission of malware is strictly prohibited. This includes software which
|
||||
does not do as it advertises, for example if it posts telemetry without stating
|
||||
clearly that it does in the package meta.
|
||||
|
||||
### 2.2. State of Completion
|
||||
|
||||
ContentDB should only currently contain playable content - content which is
|
||||
sufficiently complete to be useful to end users. It's fine to add stuff which
|
||||
is still a work in progress (WIP) as long as it adds sufficient value -
|
||||
Mineclone 2 is a good example of a WIP package which may break between releases
|
||||
MineClone 2 is a good example of a WIP package which may break between releases
|
||||
but still has value. Note that this doesn't mean that you should add a thing
|
||||
you started working on yesterday, it's worth adding all the basic stuff to
|
||||
make your package useful.
|
||||
@@ -116,15 +125,15 @@ Public domain is not a valid license in many countries, please use CC0 or MIT in
|
||||
|
||||
## 5. Promotions and Advertisements (inc. asking for donations)
|
||||
|
||||
Any information other than the long description - including screenshots - must
|
||||
not contain any promotions or advertisements. This includes asking for donations,
|
||||
promoting online shops, or linking to personal websites and social media.
|
||||
You may note place any promotions or advertisements in any meta data including
|
||||
screensthos. This includes asking for donations, promoting online shops,
|
||||
or linking to personal websites and social media. Please instead use the
|
||||
fields provided on your user profile page to place links to websites and
|
||||
donation pages.
|
||||
|
||||
ContentDB is for the community. We may remove any promotions if we feel that
|
||||
they're inappropriate.
|
||||
|
||||
Paid promotions are not allowed at all, anywhere.
|
||||
|
||||
|
||||
## 6. Reporting Violations
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ make_searchable(db.metadata)
|
||||
|
||||
|
||||
class ArticleQuery(BaseQuery, SearchQueryMixin):
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
class UserRank(enum.Enum):
|
||||
@@ -129,6 +129,10 @@ class User(db.Model, UserMixin):
|
||||
active = db.Column("is_active", db.Boolean, nullable=False, server_default="0")
|
||||
display_name = db.Column(db.String(100), nullable=False, server_default="")
|
||||
|
||||
# Links
|
||||
website_url = db.Column(db.String(255), nullable=True, default=None)
|
||||
donate_url = db.Column(db.String(255), nullable=True, default=None)
|
||||
|
||||
# Content
|
||||
notifications = db.relationship("Notification", primaryjoin="User.id==Notification.user_id")
|
||||
|
||||
@@ -417,21 +421,39 @@ class Package(db.Model):
|
||||
for e in PackagePropertyKey:
|
||||
setattr(self, e.name, getattr(package, e.name))
|
||||
|
||||
def getAsDictionaryShort(self, base_url, protonum=None):
|
||||
def getState(self):
|
||||
if self.approved:
|
||||
return "approved"
|
||||
elif self.review_thread_id:
|
||||
return "thread"
|
||||
elif (self.type == PackageType.GAME or \
|
||||
self.type == PackageType.TXP) and \
|
||||
self.screenshots.count() == 0:
|
||||
return "wip"
|
||||
elif not self.getDownloadRelease():
|
||||
return "wip"
|
||||
elif "Other" in self.license.name or "Other" in self.media_license.name:
|
||||
return "license"
|
||||
else:
|
||||
return "ready"
|
||||
|
||||
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 +476,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 +525,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 +535,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 +716,16 @@ class PackageRelease(db.Model):
|
||||
def __init__(self):
|
||||
self.releaseDate = datetime.datetime.now()
|
||||
|
||||
def approve(self, user):
|
||||
if self.package.approved and \
|
||||
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)
|
||||
|
||||
BIN
app/public/favicon-128.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
app/public/favicon-16.png
Normal file
|
After Width: | Height: | Size: 846 B |
BIN
app/public/favicon-32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 205 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 275 B After Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=6">
|
||||
<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">
|
||||
{% block headextra %}{% endblock %}
|
||||
</head>
|
||||
|
||||
@@ -126,10 +129,12 @@
|
||||
{% endblock %}
|
||||
|
||||
<footer class="container footer-copyright my-5 page-footer font-small text-center">
|
||||
ContentDB © 2018 to <a href="https://rubenwardy.com/">rubenwardy</a> |
|
||||
ContentDB © 2018-9 to <a href="https://rubenwardy.com/">rubenwardy</a> |
|
||||
<a href="https://github.com/minetest/contentdb">GitHub</a> |
|
||||
<a href="{{ url_for('flatpage', path='help') }}">Help</a> |
|
||||
<a href="{{ url_for('flatpage', path='help/reporting') }}">Report / DMCA</a>
|
||||
<a href="{{ url_for('flatpage', path='policy_and_guidance') }}">Policy and Guidance</a> |
|
||||
<a href="{{ url_for('flatpage', path='help/reporting') }}">Report / DMCA</a> |
|
||||
<a href="{{ url_for('user_list_page') }}">User List</a>
|
||||
</footer>
|
||||
|
||||
<script src="/static/jquery.min.js"></script>
|
||||
|
||||
@@ -217,6 +217,13 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if package.author.donate_url %}
|
||||
<div class="alert alert-secondary">
|
||||
Like {{ package.author.display_name }}'s work?
|
||||
<a href="{{ package.author.donate_url }}" rel="nofollow">Donate now!</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if package.type == package.type.MOD %}
|
||||
<div class="card my-4">
|
||||
<div class="card-header">Dependencies</div>
|
||||
|
||||
@@ -5,53 +5,89 @@
|
||||
{% 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">
|
||||
{% if p.getState() == "thread" %}
|
||||
<span class="mr-2 badge badge-danger">Thread</span>
|
||||
{% elif p.getState() == "ready" %}
|
||||
<span class="mr-2 badge badge-success">Ready</span>
|
||||
{% elif p.getState() == "wip" %}
|
||||
<span class="mr-2 badge badge-warning">WIP</span>
|
||||
{% elif p.getState() == "license" %}
|
||||
<span class="mr-2 badge badge-info">WIP</span>
|
||||
{% endif %}
|
||||
|
||||
{{ 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 +96,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 %}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
<a href="{{ url_for('user_profile_page', username=user.username) }}">
|
||||
{{ user.display_name }}
|
||||
</a> -
|
||||
{{ user.rank.getTitle() }} -
|
||||
{{ user.packages.count() }} packages.
|
||||
{{ user.rank.getTitle() }}
|
||||
{% if current_user.is_authenticated %}
|
||||
- {{ user.packages.count() }} packages.
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% block content %}
|
||||
|
||||
{% if not current_user.is_authenticated and user.rank == user.rank.NOT_JOINED and user.forums_username %}
|
||||
<div class="alert alert-info alert alert-info">
|
||||
<div class="alert alert-info">
|
||||
<a class="float-right btn btn-default btn-sm"
|
||||
href="{{ url_for('user_claim_page', username=user.forums_username) }}">Claim</a>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Accounts:</td>
|
||||
<td>Links:</td>
|
||||
<td>
|
||||
{% if user.forums_username %}
|
||||
<a href="https://forum.minetest.net/memberlist.php?mode=viewprofile&un={{ user.forums_username }}">
|
||||
@@ -50,7 +50,7 @@
|
||||
No forum account
|
||||
{% endif %}
|
||||
|
||||
{% if (user.forums_username and user.github_username) or user == current_user %}
|
||||
{% if user.github_username or user == current_user %}
|
||||
|
|
||||
{% endif %}
|
||||
|
||||
@@ -60,8 +60,16 @@
|
||||
<a href="{{ url_for('github_signin_page') }}">Link Github</a>
|
||||
{% endif %}
|
||||
|
||||
{% if user.website_url %}
|
||||
| <a href="{{ user.website_url }}" rel="nofollow">Website</a>
|
||||
{% endif %}
|
||||
|
||||
{% if user == current_user %}
|
||||
🌎
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
<span style="padding-right: 5px;">🌎</span>
|
||||
Visible to everyone
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -136,6 +144,8 @@
|
||||
|
||||
{% if user.checkPerm(current_user, "CHANGE_DNAME") %}
|
||||
{{ render_field(form.display_name, tabindex=230) }}
|
||||
{{ render_field(form.website_url, tabindex=232) }}
|
||||
{{ render_field(form.donate_url, tabindex=233) }}
|
||||
{% endif %}
|
||||
|
||||
{% if user.checkPerm(current_user, "CHANGE_EMAIL") %}
|
||||
@@ -158,6 +168,13 @@
|
||||
{% from "macros/packagegridtile.html" import render_pkggrid %}
|
||||
{{ render_pkggrid(packages, show_author=False) }}
|
||||
|
||||
{% if user.donate_url %}
|
||||
<div class="alert alert-secondary">
|
||||
Like {{ user.display_name }}'s work?
|
||||
<a href="{{ user.donate_url }}" rel="nofollow">Donate now!</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user == user or (current_user.is_authenticated and current_user.rank.atLeast(current_user.rank.EDITOR)) %}
|
||||
<div class="card mt-3">
|
||||
<a name="unadded-topics"></a>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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>/")
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -32,11 +32,13 @@ from app.tasks.phpbbparser import getProfile
|
||||
class UserProfileForm(FlaskForm):
|
||||
display_name = StringField("Display name", [Optional(), Length(2, 20)])
|
||||
email = StringField("Email", [Optional(), Email()])
|
||||
website_url = StringField("Website URL", [Optional(), URL()])
|
||||
donate_url = StringField("Donation URL", [Optional(), URL()])
|
||||
rank = SelectField("Rank", [Optional()], choices=UserRank.choices(), coerce=UserRank.coerce, default=UserRank.NEW_MEMBER)
|
||||
submit = SubmitField("Save")
|
||||
|
||||
|
||||
@app.route("/users/", methods=["GET"])
|
||||
@login_required
|
||||
def user_list_page():
|
||||
users = User.query.order_by(db.desc(User.rank), db.asc(User.display_name)).all()
|
||||
return render_template("users/list.html", users=users)
|
||||
@@ -60,6 +62,8 @@ def user_profile_page(username):
|
||||
# Copy form fields to user_profile fields
|
||||
if user.checkPerm(current_user, Permission.CHANGE_DNAME):
|
||||
user.display_name = form["display_name"].data
|
||||
user.website_url = form["website_url"].data
|
||||
user.donate_url = form["donate_url"].data
|
||||
|
||||
if user.checkPerm(current_user, Permission.CHANGE_RANK):
|
||||
newRank = form["rank"].data
|
||||
@@ -74,7 +78,7 @@ def user_profile_page(username):
|
||||
token = randomString(32)
|
||||
|
||||
ver = UserEmailVerification()
|
||||
ver.user = user
|
||||
ver.user = user
|
||||
ver.token = token
|
||||
ver.email = newEmail
|
||||
db.session.add(ver)
|
||||
|
||||
30
migrations/versions/d6ae9682c45f_.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: d6ae9682c45f
|
||||
Revises: 7ff57806ffd5
|
||||
Create Date: 2019-07-01 23:27:42.666877
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd6ae9682c45f'
|
||||
down_revision = '7ff57806ffd5'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('donate_url', sa.String(length=255), nullable=True))
|
||||
op.add_column('user', sa.Column('website_url', sa.String(length=255), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'website_url')
|
||||
op.drop_column('user', 'donate_url')
|
||||
# ### end Alembic commands ###
|
||||