Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce495fcd3 | ||
|
|
776a3eff2a | ||
|
|
04e8ae5bdd | ||
|
|
18b9fb3876 | ||
|
|
1da86f27a7 | ||
|
|
85340a2fe9 | ||
|
|
c4a4d9c116 | ||
|
|
87a184595c | ||
|
|
b3b1e421f2 | ||
|
|
60483ef542 | ||
|
|
3c8a8b8988 | ||
|
|
2f8bdd8f0f | ||
|
|
e87db8b87f | ||
|
|
b36273a848 | ||
|
|
7b087158d7 | ||
|
|
2fbc44bd54 | ||
|
|
950512c2a7 | ||
|
|
f4010d498f | ||
|
|
f04d4ff3cd | ||
|
|
f8b290fc45 |
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
config.cfg
|
||||
*.env
|
||||
*.sqlite
|
||||
.vscode
|
||||
custom.css
|
||||
tmp
|
||||
log.txt
|
||||
|
||||
@@ -24,6 +24,7 @@ from flaskext.markdown import Markdown
|
||||
from flask_github import GitHub
|
||||
from flask_wtf.csrf import CsrfProtect
|
||||
from flask_flatpages import FlatPages
|
||||
from flask_babel import Babel
|
||||
import os
|
||||
|
||||
app = Flask(__name__, static_folder="public/static")
|
||||
@@ -37,6 +38,7 @@ github = GitHub(app)
|
||||
csrf = CsrfProtect(app)
|
||||
mail = Mail(app)
|
||||
pages = FlatPages(app)
|
||||
babel = Babel(app)
|
||||
gravatar = Gravatar(app,
|
||||
size=58,
|
||||
rating='g',
|
||||
@@ -50,5 +52,11 @@ if not app.debug:
|
||||
from .maillogger import register_mail_error_handler
|
||||
register_mail_error_handler(app, mail)
|
||||
|
||||
|
||||
@babel.localeselector
|
||||
def get_locale():
|
||||
return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
|
||||
|
||||
|
||||
from . import models, tasks
|
||||
from .views import *
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class Permission(enum.Enum):
|
||||
APPROVE_CHANGES = "APPROVE_CHANGES"
|
||||
DELETE_PACKAGE = "DELETE_PACKAGE"
|
||||
CHANGE_AUTHOR = "CHANGE_AUTHOR"
|
||||
CHANGE_NAME = "CHANGE_NAME"
|
||||
MAKE_RELEASE = "MAKE_RELEASE"
|
||||
ADD_SCREENSHOTS = "ADD_SCREENSHOTS"
|
||||
APPROVE_SCREENSHOT = "APPROVE_SCREENSHOT"
|
||||
@@ -129,6 +130,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,6 +422,22 @@ class Package(db.Model):
|
||||
for e in PackagePropertyKey:
|
||||
setattr(self, e.name, getattr(package, e.name))
|
||||
|
||||
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)
|
||||
@@ -552,6 +573,10 @@ class Package(db.Model):
|
||||
else:
|
||||
return user.rank.atLeast(UserRank.EDITOR)
|
||||
|
||||
# Anyone can change the package name when not approved, but only editors when approved
|
||||
elif perm == Permission.CHANGE_NAME:
|
||||
return not self.approved or user.rank.atLeast(UserRank.EDITOR)
|
||||
|
||||
# Editors can change authors and approve new packages
|
||||
elif perm == Permission.APPROVE_NEW or perm == Permission.CHANGE_AUTHOR:
|
||||
return user.rank.atLeast(UserRank.EDITOR)
|
||||
@@ -697,7 +722,8 @@ class PackageRelease(db.Model):
|
||||
self.releaseDate = datetime.datetime.now()
|
||||
|
||||
def approve(self, user):
|
||||
if not self.package.checkPerm(user, Permission.APPROVE_RELEASE):
|
||||
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 != "")
|
||||
|
||||
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 |
@@ -99,11 +99,19 @@ def parseTitle(title):
|
||||
def getLinksFromModSearch():
|
||||
links = {}
|
||||
|
||||
contents = urllib.request.urlopen("https://krock-works.uk.to/minetest/modList.php").read().decode("utf-8")
|
||||
for x in json.loads(contents):
|
||||
link = x.get("link")
|
||||
if link is not None:
|
||||
links[int(x["topicId"])] = link
|
||||
try:
|
||||
contents = urllib.request.urlopen("https://krock-works.uk.to/minetest/modList.php").read().decode("utf-8")
|
||||
for x in json.loads(contents):
|
||||
try:
|
||||
link = x.get("link")
|
||||
if link is not None:
|
||||
links[int(x["topicId"])] = link
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
except urllib.error.URLError:
|
||||
print("Unable to open krocks mod search!")
|
||||
return links
|
||||
|
||||
return links
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ from app.utils import randomString
|
||||
|
||||
class GithubURLMaker:
|
||||
def __init__(self, url):
|
||||
self.baseUrl = None
|
||||
self.user = None
|
||||
self.repo = None
|
||||
|
||||
# Rewrite path
|
||||
import re
|
||||
m = re.search("^\/([^\/]+)\/([^\/]+)\/?$", url.path)
|
||||
@@ -51,6 +55,9 @@ class GithubURLMaker:
|
||||
def getScreenshotURL(self):
|
||||
return self.baseUrl + "/screenshot.png"
|
||||
|
||||
def getModConfURL(self):
|
||||
return self.baseUrl + "/mod.conf"
|
||||
|
||||
def getCommitsURL(self, branch):
|
||||
return "https://api.github.com/repos/{}/{}/commits?sha={}" \
|
||||
.format(self.user, self.repo, urllib.parse.quote_plus(branch))
|
||||
@@ -339,8 +346,11 @@ def makeVCSReleaseFromGithub(id, branch, release, url):
|
||||
raise TaskError("Invalid github repo URL")
|
||||
|
||||
commitsURL = urlmaker.getCommitsURL(branch)
|
||||
contents = urllib.request.urlopen(commitsURL).read().decode("utf-8")
|
||||
commits = json.loads(contents)
|
||||
try:
|
||||
contents = urllib.request.urlopen(commitsURL).read().decode("utf-8")
|
||||
commits = json.loads(contents)
|
||||
except HTTPError:
|
||||
raise TaskError("Unable to get commits for Github repository. Either the repository or reference doesn't exist.")
|
||||
|
||||
if len(commits) == 0 or not "sha" in commits[0]:
|
||||
raise TaskError("No commits found")
|
||||
@@ -349,7 +359,6 @@ def makeVCSReleaseFromGithub(id, branch, release, url):
|
||||
release.task_id = None
|
||||
release.commit_hash = commits[0]["sha"]
|
||||
release.approve(release.package.author)
|
||||
print(release.url)
|
||||
db.session.commit()
|
||||
|
||||
return release.url
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
<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/bootstrap.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=6">
|
||||
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=7">
|
||||
<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>
|
||||
|
||||
@@ -76,24 +79,24 @@
|
||||
<a class="nav-link" href="{{ url_for('user_profile_page', username=current_user.username) }}#unadded-topics">Your unadded topics</a>
|
||||
</li>
|
||||
{% if current_user.canAccessTodoList() %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('todo_page') }}">Work Queue</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('user_list_page') }}">User list</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('todo_page') }}">{{ _("Work Queue") }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('user_list_page') }}">{{ _("User list") }}</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('todo_topics_page') }}">All unadded topics</a>
|
||||
<a class="nav-link" href="{{ url_for('todo_topics_page') }}">{{ _("All unadded topics") }}</a>
|
||||
</li>
|
||||
{% if current_user.rank == current_user.rank.ADMIN %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin_page') }}">Admin</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin_page') }}">{{ _("Admin") }}</a></li>
|
||||
{% endif %}
|
||||
{% if current_user.rank == current_user.rank.MODERATOR %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('tag_list_page') }}">Tag Editor</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('license_list_page') }}">License Editor</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('tag_list_page') }}">{{ _("Tag Editor") }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('license_list_page') }}">{{ _("License Editor") }}</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('user.logout') }}">Sign out</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="{{ url_for('user.logout') }}">{{ _("Sign out") }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a class="nav-link" href="{{ url_for('user.login') }}">Sign in</a></li>
|
||||
<li><a class="nav-link" href="{{ url_for('user.login') }}">{{ _("Sign in") }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -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='help') }}">{{ _("Help") }}</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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Welcome
|
||||
{{ _("Welcome") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block scriptextra %}
|
||||
@@ -38,35 +38,35 @@ Welcome
|
||||
|
||||
|
||||
<a href="{{ url_for('packages_page', sort='created_at', order='desc') }}" class="btn btn-secondary float-right">
|
||||
See more
|
||||
{{ _("See more") }}
|
||||
</a>
|
||||
<h2 class="my-3">Recently Added</h2>
|
||||
<h2 class="my-3">{{ _("Recently Added") }}</h2>
|
||||
{{ render_pkggrid(new) }}
|
||||
|
||||
|
||||
<a href="{{ url_for('packages_page', type='mod', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||
See more
|
||||
{{ _("See more") }}
|
||||
</a>
|
||||
<h2 class="my-3">Top Mods</h2>
|
||||
<h2 class="my-3">{{ _("Top Mods") }}</h2>
|
||||
{{ render_pkggrid(pop_mod) }}
|
||||
|
||||
|
||||
<a href="{{ url_for('packages_page', type='game', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||
See more
|
||||
{{ _("See more") }}
|
||||
</a>
|
||||
<h2 class="my-3">Top Games</h2>
|
||||
<h2 class="my-3">{{ _("Top Games") }}</h2>
|
||||
{{ render_pkggrid(pop_gam) }}
|
||||
|
||||
|
||||
<a href="{{ url_for('packages_page', type='txp', sort='score', order='desc') }}" class="btn btn-secondary float-right">
|
||||
See more
|
||||
{{ _("See more") }}
|
||||
</a>
|
||||
<h2 class="my-3">Top Texture Packs</h2>
|
||||
<h2 class="my-3">{{ _("Top Texture Packs") }}</h2>
|
||||
{{ render_pkggrid(pop_txp) }}
|
||||
|
||||
<div class="text-center">
|
||||
<small>
|
||||
CDB has {{ count }} packages, with a total of {{ downloads }} downloads.
|
||||
{{ _("CDB has %(count)d packages, with a total of %(downloads)d downloads.", count=count, downloads=downloads) }}
|
||||
</small>
|
||||
</div>
|
||||
<!-- </main> -->
|
||||
|
||||
@@ -20,19 +20,19 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Create Package</h1>
|
||||
<h1>{{ _("Create Package") }}</h1>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<a class="float-right btn btn-sm btn-default" href="{{ url_for('flatpage', path='policy_and_guidance') }}">View</a>
|
||||
<a class="float-right btn btn-sm btn-default" href="{{ url_for('flatpage', path='policy_and_guidance') }}">{{ _("View") }}</a>
|
||||
|
||||
Have you read the Package Inclusion Policy and Guidance yet?
|
||||
{{ _("Have you read the Package Inclusion Policy and Guidance yet?") }}
|
||||
</div>
|
||||
|
||||
<noscript>
|
||||
<div class="alert alert-warning">
|
||||
Javascript is needed to improve the user interface, and is needed for features
|
||||
such as finding metadata from git, and autocompletion.<br />
|
||||
Whilst disabled Javascript may work, it is not officially supported.
|
||||
{{ _("Javascript is needed to improve the user interface, and is needed for features
|
||||
such as finding metadata from git, and autocompletion.") }}<br />
|
||||
{{ _("Whilst disabled Javascript may work, it is not officially supported.") }}
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
@@ -42,12 +42,16 @@
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<fieldset>
|
||||
<legend>Package</legend>
|
||||
<legend>{{ _("Package") }}</legend>
|
||||
|
||||
<div class="row">
|
||||
{{ render_field(form.type, class_="pkg_meta col-sm-2") }}
|
||||
{{ render_field(form.title, class_="pkg_meta col-sm-7") }}
|
||||
{{ render_field(form.name, class_="pkg_meta col-sm-3") }}
|
||||
{% if package and package.approved and not package.checkPerm(current_user, "CHANGE_NAME") %}
|
||||
{{ render_field(form.name, class_="pkg_meta col-sm-3", readonly=True) }}
|
||||
{% else %}
|
||||
{{ render_field(form.name, class_="pkg_meta col-sm-3") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ render_field(form.short_desc, class_="pkg_meta") }}
|
||||
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
||||
@@ -55,11 +59,15 @@
|
||||
{{ render_field(form.license, class_="not_txp col-sm-6") }}
|
||||
{{ render_field(form.media_license, class_="col-sm-6") }}
|
||||
</div>
|
||||
<div class="pkg_meta row">
|
||||
<div class="not_txp col-sm-6"></div>
|
||||
<div class="not_txp col-sm-6">{{ _("If there is no media, set the Media License to the same as the License.") }}</div>
|
||||
</div>
|
||||
{{ render_field(form.desc, class_="pkg_meta", fieldclass="form-control markdown") }}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="pkg_meta">
|
||||
<legend class="not_txp">Dependencies</legend>
|
||||
<legend class="not_txp">{{ _("Dependencies") }}</legend>
|
||||
|
||||
{{ render_mpackage_field(form.provides_str, class_="not_txp", placeholder="Comma separated list") }}
|
||||
{{ render_deps_field(form.harddep_str, class_="not_txp not_game", placeholder="Comma separated list") }}
|
||||
@@ -67,30 +75,29 @@
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend class="pkg_meta">Repository and Links</legend>
|
||||
<legend class="pkg_meta">{{ _("Repository and Links") }}</legend>
|
||||
|
||||
<div class="pkg_wiz_1">
|
||||
<p>Enter the repo URL for the package.
|
||||
If the repo uses git then the metadata will be automatically imported.</p>
|
||||
<p>{{ _("Enter the repo URL for the package.
|
||||
If the repo uses git then the metadata will be automatically imported.") }}</p>
|
||||
|
||||
<p>Leave blank if you don't have a repo. Click skip if the import fails.</p>
|
||||
<p>{{ _("Leave blank if you don't have a repo. Click skip if the import fails.") }}</p>
|
||||
</div>
|
||||
|
||||
{{ render_field(form.repo, class_="pkg_repo") }}
|
||||
|
||||
|
||||
<div class="pkg_wiz_1">
|
||||
<a id="pkg_wiz_1_next" class="btn btn-primary">Next (Autoimport)</a>
|
||||
<a id="pkg_wiz_1_skip" class="btn btn-default">Skip Autoimport</a>
|
||||
<a id="pkg_wiz_1_next" class="btn btn-primary">{{ _("Next (Autoimport)") }}</a>
|
||||
<a id="pkg_wiz_1_skip" class="btn btn-default">{{ _("Skip Autoimport") }}</a>
|
||||
</div>
|
||||
|
||||
<div class="pkg_wiz_2">
|
||||
Importing... (This may take a while)
|
||||
{{ _("Importing... (This may take a while)") }}
|
||||
</div>
|
||||
|
||||
{{ render_field(form.website, class_="pkg_meta") }}
|
||||
{{ render_field(form.issueTracker, class_="pkg_meta") }}
|
||||
{{ render_field(form.forums, class_="pkg_meta", placeholder="Tip: paste in a forum topic URL") }}
|
||||
{{ render_field(form.forums, class_="pkg_meta", placeholder=_("Tip: paste in a forum topic URL")) }}
|
||||
</fieldset>
|
||||
|
||||
<div class="pkg_meta">{{ render_submit_field(form.submit) }}</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ render_field(form.vcsLabel, class_="mt-3") }}
|
||||
{% endif %}
|
||||
|
||||
{{ render_field(form.fileUpload, fieldclass="form-control-file", class_="mt-3") }}
|
||||
{{ render_field(form.fileUpload, fieldclass="form-control-file", class_="mt-3", accept=".zip") }}
|
||||
|
||||
<div class="row">
|
||||
{{ render_field(form.min_rel, class_="col-sm-6") }}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ render_field(form.title) }}
|
||||
{{ render_field(form.fileUpload, fieldclass="form-control-file") }}
|
||||
{{ render_field(form.fileUpload, fieldclass="form-control-file", accept="image/png,image/jpeg") }}
|
||||
{{ render_submit_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if package and current_user != package.author and not current_user.rank.atLeast(current_user.rank.EDITOR) %}
|
||||
{% if package.issueTracker %}
|
||||
<div class="alert alert-warning">
|
||||
Found a bug? Post on the <a href="{{ package.issue_tracker }}">issue tracker</a> instead.<br />
|
||||
{% if package and current_user != package.author and not current_user.rank.atLeast(current_user.rank.EDITOR) and package.issueTracker %}
|
||||
<div class="alert alert-warning">
|
||||
Found a bug? Post on the <a href="{{ package.issueTracker }}">issue tracker</a> instead.<br />
|
||||
If the package shouldn't be on CDB - for example, if it doesn't work at all - then please let us know here.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% from "macros/forms.html" import render_field, render_submit_field, render_checkbox_field %}
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
<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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
26
app/utils.py
@@ -20,7 +20,7 @@ from flask_user import *
|
||||
from flask_login import login_user, logout_user
|
||||
from app.models import *
|
||||
from app import app
|
||||
import random, string, os
|
||||
import random, string, os, imghdr
|
||||
|
||||
def getExtension(filename):
|
||||
return filename.rsplit(".", 1)[1].lower() if "." in filename else None
|
||||
@@ -28,6 +28,10 @@ def getExtension(filename):
|
||||
def isFilenameAllowed(filename, exts):
|
||||
return getExtension(filename) in exts
|
||||
|
||||
ALLOWED_IMAGES = set(["jpeg", "png"])
|
||||
def isAllowedImage(data):
|
||||
return imghdr.what(None, data) in ALLOWED_IMAGES
|
||||
|
||||
def shouldReturnJson():
|
||||
return "application/json" in request.accept_mimetypes and \
|
||||
not "text/html" in request.accept_mimetypes
|
||||
@@ -36,16 +40,32 @@ def randomString(n):
|
||||
return ''.join(random.choice(string.ascii_lowercase + \
|
||||
string.ascii_uppercase + string.digits) for _ in range(n))
|
||||
|
||||
def doFileUpload(file, allowedExtensions, fileTypeName):
|
||||
def doFileUpload(file, fileType, fileTypeDesc):
|
||||
if not file or file is None or file.filename == "":
|
||||
flash("No selected file", "error")
|
||||
return None
|
||||
|
||||
allowedExtensions = []
|
||||
isImage = False
|
||||
if fileType == "image":
|
||||
allowedExtensions = ["jpg", "jpeg", "png"]
|
||||
isImage = True
|
||||
elif fileType == "zip":
|
||||
allowedExtensions = ["zip"]
|
||||
else:
|
||||
raise Exception("Invalid fileType")
|
||||
|
||||
ext = getExtension(file.filename)
|
||||
if ext is None or not ext in allowedExtensions:
|
||||
flash("Please upload load " + fileTypeName, "error")
|
||||
flash("Please upload load " + fileTypeDesc, "danger")
|
||||
return None
|
||||
|
||||
if isImage and not isAllowedImage(file.stream.read()):
|
||||
flash("Uploaded image isn't actually an image", "danger")
|
||||
return None
|
||||
|
||||
file.stream.seek(0)
|
||||
|
||||
filename = randomString(10) + "." + ext
|
||||
file.save(os.path.join("app/public/uploads", filename))
|
||||
return "/uploads/" + filename
|
||||
|
||||
@@ -243,12 +243,21 @@ def create_edit_package_page(author=None, name=None):
|
||||
package = Package()
|
||||
package.author = author
|
||||
wasNew = True
|
||||
|
||||
elif package.approved and package.name != form.name.data and \
|
||||
not package.checkPerm(current_user, Permission.CHANGE_NAME):
|
||||
flash("Unable to change package name", "danger")
|
||||
return redirect(url_for("create_edit_package_page", author=author, name=name))
|
||||
|
||||
else:
|
||||
triggerNotif(package.author, current_user,
|
||||
"{} edited".format(package.title), package.getDetailsURL())
|
||||
|
||||
form.populate_obj(package) # copy to row
|
||||
|
||||
if package.type== PackageType.TXP:
|
||||
package.license = package.media_license
|
||||
|
||||
mpackage_cache = {}
|
||||
package.provides.clear()
|
||||
mpackages = MetaPackage.SpecToList(form.provides_str.data, mpackage_cache)
|
||||
|
||||
@@ -96,7 +96,7 @@ def create_release_page(package):
|
||||
|
||||
return redirect(url_for("check_task", id=rel.task_id, r=rel.getEditURL()))
|
||||
else:
|
||||
uploadedPath = doFileUpload(form.fileUpload.data, ["zip"], "a zip file")
|
||||
uploadedPath = doFileUpload(form.fileUpload.data, "zip", "a zip file")
|
||||
if uploadedPath is not None:
|
||||
rel = PackageRelease()
|
||||
rel.package = package
|
||||
|
||||
@@ -49,7 +49,7 @@ def create_screenshot_page(package, id=None):
|
||||
# Initial form class from post data and default data
|
||||
form = CreateScreenshotForm()
|
||||
if request.method == "POST" and form.validate():
|
||||
uploadedPath = doFileUpload(form.fileUpload.data, ["png", "jpg", "jpeg"],
|
||||
uploadedPath = doFileUpload(form.fileUpload.data, "image",
|
||||
"a PNG or JPG image file")
|
||||
if uploadedPath is not None:
|
||||
ss = PackageScreenshot()
|
||||
|
||||
@@ -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)
|
||||
|
||||
3
babel.cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
[python: app/**.py]
|
||||
[jinja2: app/templates/**.html]
|
||||
extensions=jinja2.ext.autoescape,jinja2.ext.with_
|
||||
@@ -23,3 +23,7 @@ MAIL_SERVER=""
|
||||
MAIL_PORT=587
|
||||
MAIL_USE_TLS=True
|
||||
MAIL_UTILS_ERROR_SEND_TO=[""]
|
||||
|
||||
LANGUAGES = {
|
||||
'en': 'English',
|
||||
}
|
||||
|
||||
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 ###
|
||||
@@ -7,11 +7,13 @@ Flask-Menu~=0.7
|
||||
Flask-Migrate~=2.3
|
||||
Flask-SQLAlchemy~=2.3
|
||||
Flask-User~=0.6
|
||||
Flask-Babel
|
||||
GitHub-Flask~=3.2
|
||||
SQLAlchemy-Searchable==1.0.3
|
||||
|
||||
beautifulsoup4~=4.6
|
||||
celery~=4.2
|
||||
celery==4.1.1
|
||||
kombu==4.2.0
|
||||
GitPython~=2.1
|
||||
lxml~=4.2
|
||||
pillow~=5.3
|
||||
|
||||